From 964111a3b6387436aeff4772b29e99b16ba2a2e1 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Mon, 24 Oct 2016 09:54:10 +0100 Subject: [PATCH 1/8] Initial tabbable search results implementation Refs #371 --- ...lfresco-search-autocomplete.component.html | 4 +-- .../alfresco-search-autocomplete.component.ts | 32 +++++++++++++++++++ .../alfresco-search-control.component.html | 2 +- .../alfresco-search-control.component.ts | 8 +++++ .../components/alfresco-search.component.html | 2 +- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html index 66fcf83db3..bbb1b5ea82 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html @@ -1,7 +1,7 @@ - - + - + From 8bfe58c2a79120cbdcf49b64897672057f857680 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Tue, 25 Oct 2016 18:08:57 +0100 Subject: [PATCH 2/8] New implementation of focus/blur event management using Obserable Refs #371 --- .../assets/alfresco-search.component.mock.ts | 76 +++++++++++ ...lfresco-search-autocomplete.component.html | 4 +- ...esco-search-autocomplete.component.spec.ts | 124 +++++++++--------- .../alfresco-search-autocomplete.component.ts | 21 +-- .../alfresco-search-control.component.html | 3 +- .../alfresco-search-control.component.spec.ts | 75 +++++++---- .../alfresco-search-control.component.ts | 52 ++++++-- 7 files changed, 241 insertions(+), 114 deletions(-) create mode 100644 ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts diff --git a/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts b/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts new file mode 100644 index 0000000000..32975ae655 --- /dev/null +++ b/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts @@ -0,0 +1,76 @@ +/*! + * @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. + */ + +export var result = { + list: { + entries: [ + { + entry: { + id: '123', + name: 'MyDoc', + isFile : true, + content: { + mimeType: 'text/plain' + }, + createdByUser: { + displayName: 'John Doe' + }, + modifiedByUser: { + displayName: 'John Doe' + } + } + } + ] + } +}; + +export var folderResult = { + list: { + entries: [ + { + entry: { + id: '123', + name: 'MyFolder', + isFile : false, + isFolder : true, + createdByUser: { + displayName: 'John Doe' + }, + modifiedByUser: { + displayName: 'John Doe' + } + } + } + ] + } +}; + +export var noResult = { + list: { + entries: [] + } +}; + +export var errorJson = { + error: { + errorKey: 'Search failed', + statusCode: 400, + briefSummary: '08220082 search failed', + stackTrace: 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.', + descriptionURL: 'https://api-explorer.alfresco.com' + } +}; diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html index bbb1b5ea82..52a4ddaeb3 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html @@ -1,7 +1,7 @@
{{getMimeTypeKey(result)|translate}} diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index 31f046b96e..a5bccf9a55 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -43,6 +43,9 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { @Output() preview: EventEmitter = new EventEmitter(); + @Output() + blurEmitter: EventEmitter = new EventEmitter(); + @Output() resultsEmitter = new EventEmitter(); @@ -129,4 +132,33 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { } } + onRowBlur(node): void { + window.setTimeout(() => { + let focusedEl = document.activeElement; + if (focusedEl && focusedEl.id && focusedEl.id.indexOf('result_row_') === 0) { + return; + } + this.blurEmitter.emit(node); + }, 100); + console.log('row blur', node); + } + + onRowFocus(node): void { + console.log('row focus', node); + } + + onRowEnter(node): void { + if (node && node.entry) { + if (node.entry.isFile) { + this.preview.emit({ + value: node + }); + } + } + } + + onFocusOut(): void { + console.log('onfocusout'); + } + } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html index 868063b0f5..b175a5926c 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html @@ -24,4 +24,4 @@ + (preview)="onFileClicked($event)" (blurEmitter)="onAutoCompleteBlur($event)"> diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts index 3c44af48f9..d51e198219 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts @@ -138,6 +138,10 @@ export class AlfrescoSearchControlComponent implements OnInit { onBlur(): void { window.setTimeout(() => { + let focusedEl = document.activeElement; + if (focusedEl && focusedEl.id && focusedEl.id.indexOf('result_row_') === 0) { + return; + } this.searchActive = false; }, 200); if (this.expandable && (this.searchControl.value === '' || this.searchControl.value === undefined)) { @@ -155,4 +159,8 @@ export class AlfrescoSearchControlComponent implements OnInit { this.searchActive = true; } + onAutoCompleteBlur(): void { + this.searchActive = false; + } + } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html index 224ab2ec98..75cfea1fb4 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html @@ -16,7 +16,7 @@
{{getMimeTypeKey(result)|translate}} {{result.entry.name}}
- - +
{{getMimeTypeKey(result)|translate}} diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts index d88a184b92..5c0324e498 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts @@ -19,6 +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 { AlfrescoSearchService } from '../services/alfresco-search.service'; import { AlfrescoApiService, @@ -34,66 +35,6 @@ describe('AlfrescoSearchAutocompleteComponent', () => { let fixture: ComponentFixture, element: HTMLElement; let component: AlfrescoSearchAutocompleteComponent; - let result = { - list: { - entries: [ - { - entry: { - id: '123', - name: 'MyDoc', - isFile : true, - content: { - mimeType: 'text/plain' - }, - createdByUser: { - displayName: 'John Doe' - }, - modifiedByUser: { - displayName: 'John Doe' - } - } - } - ] - } - }; - - let folderResult = { - list: { - entries: [ - { - entry: { - id: '123', - name: 'MyFolder', - isFile : false, - isFolder : true, - createdByUser: { - displayName: 'John Doe' - }, - modifiedByUser: { - displayName: 'John Doe' - } - } - } - ] - } - }; - - let noResult = { - list: { - entries: [] - } - }; - - let errorJson = { - error: { - errorKey: 'Search failed', - statusCode: 400, - briefSummary: '08220082 search failed', - stackTrace: 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.', - descriptionURL: 'https://api-explorer.alfresco.com' - } - }; - beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ @@ -286,4 +227,67 @@ describe('AlfrescoSearchAutocompleteComponent', () => { component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}}); }); + it('should emit preview when enter key pressed when a file item is in focus', (done) => { + + let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); + spyOn(searchService, 'getSearchNodesPromise') + .and.returnValue(Promise.resolve(result)); + + component.resultsEmitter.subscribe(x => { + fixture.detectChanges(); + ( element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', { + key: 'Enter' + })); + }); + + 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(); + ( 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(); + ( 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(); + }); + }); + }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index a5bccf9a55..71eb6411a1 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -44,7 +44,7 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { preview: EventEmitter = new EventEmitter(); @Output() - blurEmitter: EventEmitter = new EventEmitter(); + focusEmitter: EventEmitter = new EventEmitter(); @Output() resultsEmitter = new EventEmitter(); @@ -132,19 +132,12 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { } } - onRowBlur(node): void { - window.setTimeout(() => { - let focusedEl = document.activeElement; - if (focusedEl && focusedEl.id && focusedEl.id.indexOf('result_row_') === 0) { - return; - } - this.blurEmitter.emit(node); - }, 100); - console.log('row blur', node); + onRowFocus($event: FocusEvent): void { + this.focusEmitter.emit($event); } - onRowFocus(node): void { - console.log('row focus', node); + onRowBlur($event: FocusEvent): void { + this.focusEmitter.emit($event); } onRowEnter(node): void { @@ -157,8 +150,4 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { } } - onFocusOut(): void { - console.log('onfocusout'); - } - } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html index b175a5926c..36236f9dfd 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html @@ -24,4 +24,5 @@ + (preview)="onFileClicked($event)" + (focusEmitter)="onAutoCompleteFocus($event)"> diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts index b8321de775..76eee29fe3 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts @@ -20,6 +20,7 @@ import { AlfrescoSearchControlComponent } from './alfresco-search-control.compon import { AlfrescoSearchAutocompleteComponent } from './alfresco-search-autocomplete.component'; import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service'; import { TranslationMock } from './../assets/translation.service.mock'; +import { result } from './../assets/alfresco-search.component.mock'; import { AlfrescoSettingsService, AlfrescoApiService, @@ -150,24 +151,45 @@ describe('AlfrescoSearchControlComponent', () => { expect(autocomplete.classList.contains('active')).toBe(false); }); - it('should make find-as-you-type control visible when search box has focus', () => { + it('should make find-as-you-type control visible when search box has focus', (done) => { fixture.detectChanges(); - inputEl.dispatchEvent(new Event('focus')); - fixture.detectChanges(); - let autocomplete: Element = element.querySelector('alfresco-search-autocomplete'); - expect(autocomplete.classList.contains('active')).toBe(true); + inputEl.dispatchEvent(new FocusEvent('focus')); + window.setTimeout(() => { // wait for debounce() to complete + fixture.detectChanges(); + let autocomplete: Element = element.querySelector('alfresco-search-autocomplete'); + expect(autocomplete.classList.contains('active')).toBe(true); + done(); + }, 100); }); it('should hide find-as-you-type results when the search box loses focus', (done) => { fixture.detectChanges(); - inputEl.dispatchEvent(new Event('focus')); - inputEl.dispatchEvent(new Event('blur')); + inputEl.dispatchEvent(new FocusEvent('focus')); + inputEl.dispatchEvent(new FocusEvent('blur')); window.setTimeout(() => { fixture.detectChanges(); let autocomplete: Element = element.querySelector('alfresco-search-autocomplete'); expect(autocomplete.classList.contains('active')).toBe(false); done(); - }, 250); + }, 100); + }); + + it('should keep find-as-you-type control visible when user tabs into results', (done) => { + let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); + spyOn(searchService, 'getSearchNodesPromise') + .and.returnValue(Promise.resolve(result)); + + fixture.detectChanges(); + inputEl.dispatchEvent(new FocusEvent('focus')); + fixture.detectChanges(); + inputEl.dispatchEvent(new FocusEvent('blur')); + component.onAutoCompleteFocus(new FocusEvent('focus')); + window.setTimeout(() => { // wait for debounce() to complete + fixture.detectChanges(); + let autocomplete: Element = element.querySelector('alfresco-search-autocomplete'); + expect(autocomplete.classList.contains('active')).toBe(true); + done(); + }, 100); }); it('should hide find-as-you-type results when escape key pressed', () => { @@ -239,32 +261,41 @@ describe('AlfrescoSearchControlComponent', () => { describe('component focus', () => { - it('should fire an event when the search box receives focus', () => { + it('should fire an event when the search box receives focus', (done) => { spyOn(component.expand, 'emit'); let inputEl: HTMLElement = element.querySelector('input'); - inputEl.dispatchEvent(new Event('focus')); - expect(component.expand.emit).toHaveBeenCalledWith({ - expanded: true - }); + inputEl.dispatchEvent(new FocusEvent('focus')); + window.setTimeout(() => { + expect(component.expand.emit).toHaveBeenCalledWith({ + expanded: true + }); + done(); + }, 100); }); - it('should fire an event when the search box loses focus', () => { + it('should fire an event when the search box loses focus', (done) => { spyOn(component.expand, 'emit'); let inputEl: HTMLElement = element.querySelector('input'); - inputEl.dispatchEvent(new Event('blur')); - expect(component.expand.emit).toHaveBeenCalledWith({ - expanded: false - }); + inputEl.dispatchEvent(new FocusEvent('blur')); + window.setTimeout(() => { + expect(component.expand.emit).toHaveBeenCalledWith({ + expanded: false + }); + done(); + }, 100); }); it('should NOT fire an event when the search box receives/loses focus but the component is not expandable', - () => { + (done) => { spyOn(component.expand, 'emit'); component.expandable = false; let inputEl: HTMLElement = element.querySelector('input'); - inputEl.dispatchEvent(new Event('focus')); - inputEl.dispatchEvent(new Event('blur')); - expect(component.expand.emit).not.toHaveBeenCalled(); + inputEl.dispatchEvent(new FocusEvent('focus')); + inputEl.dispatchEvent(new FocusEvent('blur')); + window.setTimeout(() => { + expect(component.expand.emit).not.toHaveBeenCalled(); + done(); + }, 100); }); }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts index d51e198219..ad6284c79f 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts @@ -16,9 +16,10 @@ */ import { FormControl, Validators } from '@angular/forms'; -import { Component, Input, Output, OnInit, ElementRef, EventEmitter, ViewChild } from '@angular/core'; +import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter, ViewChild } from '@angular/core'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { SearchTermValidator } from './../forms/search-term-validator'; +import { Observable, Subject } from 'rxjs/Rx'; @Component({ moduleId: module.id, @@ -26,7 +27,7 @@ import { SearchTermValidator } from './../forms/search-term-validator'; templateUrl: './alfresco-search-control.component.html', styleUrls: ['./alfresco-search-control.component.css'] }) -export class AlfrescoSearchControlComponent implements OnInit { +export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { @Input() searchTerm = ''; @@ -66,6 +67,8 @@ export class AlfrescoSearchControlComponent implements OnInit { searchValid = false; + private focusSubject = new Subject(); + constructor(private translate: AlfrescoTranslationService) { this.searchControl = new FormControl( @@ -80,9 +83,16 @@ export class AlfrescoSearchControlComponent implements OnInit { this.onSearchTermChange(value); } ); + + this.setupFocusEventHandlers(); + this.translate.addTranslationFolder('node_modules/ng2-alfresco-search/dist/src'); } + ngOnDestroy(): void { + this.focusSubject.unsubscribe(); + } + private onSearchTermChange(value: string): void { this.searchActive = true; this.autocompleteSearchTerm = value; @@ -94,6 +104,20 @@ export class AlfrescoSearchControlComponent implements OnInit { }); } + private setupFocusEventHandlers() { + let focusEvents: Observable = this.focusSubject.asObservable().debounceTime(50); + focusEvents.filter(($event: FocusEvent) => { + return $event.type === 'focusin' || $event.type === 'focus'; + }).subscribe(($event) => { + this.onSearchFocus($event); + }); + focusEvents.filter(($event: any) => { + return $event.type === 'focusout' || $event.type === 'blur'; + }).subscribe(($event) => { + this.onSearchBlur($event); + }); + } + getTextFieldClassName(): string { return 'mdl-textfield mdl-js-textfield' + (this.expandable ? ' mdl-textfield--expandable' : ''); } @@ -127,28 +151,30 @@ export class AlfrescoSearchControlComponent implements OnInit { }); } - onFocus(): void { + onSearchFocus($event): void { this.searchActive = true; + } + + onSearchBlur($event): void { + this.searchActive = false; + } + + onFocus($event): void { if (this.expandable) { this.expand.emit({ expanded: true }); } + this.focusSubject.next($event); } - onBlur(): void { - window.setTimeout(() => { - let focusedEl = document.activeElement; - if (focusedEl && focusedEl.id && focusedEl.id.indexOf('result_row_') === 0) { - return; - } - this.searchActive = false; - }, 200); + onBlur($event): void { if (this.expandable && (this.searchControl.value === '' || this.searchControl.value === undefined)) { this.expand.emit({ expanded: false }); } + this.focusSubject.next($event); } onEscape(): void { @@ -159,8 +185,8 @@ export class AlfrescoSearchControlComponent implements OnInit { this.searchActive = true; } - onAutoCompleteBlur(): void { - this.searchActive = false; + onAutoCompleteFocus($event): void { + this.focusSubject.next($event); } } From b90c676bc9dff24fed8920194a18f93b90567f29 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Tue, 25 Oct 2016 18:10:26 +0100 Subject: [PATCH 3/8] Remove duplicated error message --- .../src/components/alfresco-search-autocomplete.component.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html index 52a4ddaeb3..112efa8329 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html @@ -29,5 +29,3 @@
-

{{ 'SEARCH.RESULTS.ERROR' | - translate:{errorMessage: errorMessage} }}

From 68fc93ecbcb3a8d5fe9efca00364ac26549a317c Mon Sep 17 00:00:00 2001 From: Will Abson Date: Tue, 25 Oct 2016 18:10:53 +0100 Subject: [PATCH 4/8] Add additional test for no server error --- .../src/services/alfresco-search.service.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts b/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts index 56f0093492..814737874d 100644 --- a/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts @@ -77,4 +77,16 @@ describe('AlfrescoSearchService', () => { ); }); + it('should notify a general error if the API does not return a specific error', (done) => { + spyOn(fakeApi.core.searchApi, 'liveSearchNodes').and.returnValue(Promise.reject(null)); + service.getLiveSearchResults('').subscribe( + () => {}, + (res: any) => { + expect(res).toBeDefined(); + expect(res).toEqual('Server error'); + done(); + } + ); + }); + }); From a8bea1800b161e659f14d369596d987f31cf8484 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Wed, 26 Oct 2016 12:56:42 +0100 Subject: [PATCH 5/8] Allow use of arrow keys to navigate FAYT results and update tests Refs #371 --- .../assets/alfresco-search.component.mock.ts | 44 +- ...lfresco-search-autocomplete.component.html | 12 +- ...esco-search-autocomplete.component.spec.ts | 485 +++++++++++------- .../alfresco-search-autocomplete.component.ts | 48 +- .../alfresco-search-control.component.html | 6 +- .../alfresco-search-control.component.spec.ts | 36 ++ .../alfresco-search-control.component.ts | 39 +- 7 files changed, 453 insertions(+), 217 deletions(-) diff --git a/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts b/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts index 32975ae655..edd563beb7 100644 --- a/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts +++ b/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.component.mock.ts @@ -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 ] } }; diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html index 112efa8329..af3d623fc0 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.html @@ -1,8 +1,14 @@ - - + +
{{getMimeTypeKey(result)|translate}}
{{result.entry.name}}
diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts index 5c0324e498..684e4008ad 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts @@ -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, 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('MyDoc'); + 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 = 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('MyDoc'); - 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 = 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 = 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 = 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(); + ( 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(); + ( 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(); + ( 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(); + ( 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 = 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(); + ( 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 = 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(); - ( 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(); + ( 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(); - ( 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(); - ( 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(); - ( 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(); - ( 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(); - }); }); }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index 71eb6411a1..fa57baf510 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -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 = new EventEmitter(); + @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'); + ( 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( $event.target); + if (nextElement) { + ( nextElement).focus(); + } + } + + onRowArrowUp($event: KeyboardEvent): void { + let previousElement = this.getPreviousElementSibling( $event.target); + if (previousElement) { + ( previousElement).focus(); + } else { + this.scrollBackEmitter.emit($event); + } + } + + onRowEscape($event: KeyboardEvent): void { + this.cancelEmitter.emit($event); + } + } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html index 36236f9dfd..ab75efb3e5 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html @@ -22,7 +22,9 @@ - + (focusEmitter)="onAutoCompleteFocus($event)" + (scrollBackEmitter)="onAutoCompleteReturn($event)" + (cancelEmitter)="onAutoCompleteCancel($event)"> diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts index 76eee29fe3..dd7b0f1157 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts @@ -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(); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts index ad6284c79f..01f46edf71 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts @@ -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) { + ( this.searchInput.nativeElement).focus(); + } + } + + onAutoCompleteCancel($event): void { + if (this.searchInput) { + ( this.searchInput.nativeElement).focus(); + } + this.setAutoCompleteDisplayed(false); + } + } From 3f440215b987813130cc15f3465bf5153c3f2dc3 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Wed, 26 Oct 2016 13:10:56 +0100 Subject: [PATCH 6/8] Ensure maximum number of results is shown in FAYT results Refs #371 --- ...alfresco-search-autocomplete.component.spec.ts | 15 +++++++++++++++ .../alfresco-search-autocomplete.component.ts | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts index 684e4008ad..c2e416897e 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts @@ -110,6 +110,21 @@ describe('AlfrescoSearchAutocompleteComponent', () => { updateSearchTerm('searchTerm'); }); + it('should limit the number of returned search results to the configured maximum', (done) => { + + spyOn(searchService, 'getSearchNodesPromise') + .and.returnValue(Promise.resolve(results)); + + component.resultsEmitter.subscribe(() => { + fixture.detectChanges(); + expect(element.querySelectorAll('table[data-automation-id="autocomplete_results"] tbody tr').length).toBe(2); + done(); + }); + + component.maxResults = 2; + updateSearchTerm('searchTerm'); + }); + it('should display the correct thumbnail for result items', (done) => { spyOn(searchService, 'getSearchNodesPromise') diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index fa57baf510..d0441a6aa9 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -40,6 +40,9 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { @Input() ngClass: any; + @Input() + maxResults: number = 5; + @Output() preview: EventEmitter = new EventEmitter(); @@ -83,13 +86,13 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { * Loads and displays search results * @param searchTerm Search query entered by user */ - public displaySearchResults(searchTerm) { + private displaySearchResults(searchTerm) { if (searchTerm !== null && searchTerm !== '') { this.alfrescoSearchService .getLiveSearchResults(searchTerm) .subscribe( results => { - this.results = results.list.entries; + this.results = results.list.entries.slice(0, this.maxResults); this.errorMessage = null; this.resultsEmitter.emit(this.results); }, From c10041253844e7c3da94069c81d558b8a6dff466 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Mon, 31 Oct 2016 16:36:20 +0000 Subject: [PATCH 7/8] Rename search outputs after feedback from @DenysVuyka Refs #371 --- .../search/search-bar.component.html | 2 +- ng2-components/ng2-alfresco-search/README.md | 5 +- ...esco-search-autocomplete.component.spec.ts | 52 +++++++++---------- .../alfresco-search-autocomplete.component.ts | 29 +++++------ .../alfresco-search-control.component.html | 8 +-- .../alfresco-search-control.component.spec.ts | 6 +-- .../alfresco-search-control.component.ts | 4 +- .../alfresco-search.component.spec.ts | 14 ++--- .../components/alfresco-search.component.ts | 9 ++-- 9 files changed, 63 insertions(+), 66 deletions(-) diff --git a/demo-shell-ng2/app/components/search/search-bar.component.html b/demo-shell-ng2/app/components/search/search-bar.component.html index 52d8ee7a52..b890c54210 100644 --- a/demo-shell-ng2/app/components/search/search-bar.component.html +++ b/demo-shell-ng2/app/components/search/search-bar.component.html @@ -1,5 +1,5 @@ + (searchSubmit)="onSearchSubmit($event);" (searchChange)="onSearchTermChange($event);" (expand)="onExpandToggle($event);" (fileSelect)="onFileClicked($event)"> + (searchSubmit)="onSearchSubmit($event);" + (fileSelect)="onSearchResultSelect($event);"> ``` @@ -172,6 +173,8 @@ bootstrap(SearchDemo, [ **searchChange**: Emitted when the search term is changed. The search term is provided in the 'value' property of the returned object. If the term is at less than three characters in length then the term is truncated to an empty string.
**searchSubmit**: Emitted when the search form is submitted. The search term is provided in the 'value' property of the returned object.
+**fileSelect**: Emitted when a file item from the list of find-as-you-type results is selected +**expand**: Emitted when the expanded state of the control changes based on focus events and the content of the input control #### Options diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts index c2e416897e..43f9e89abb 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts @@ -100,7 +100,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(result)); - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); expect( element.querySelector('#result_user_0').innerHTML).toBe('John Doe'); expect( element.querySelector('#result_name_0').innerHTML).toBe('MyDoc'); @@ -115,7 +115,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(results)); - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); expect(element.querySelectorAll('table[data-automation-id="autocomplete_results"] tbody tr').length).toBe(2); done(); @@ -136,7 +136,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('fake-type-icon.svg'); spyOn(thumbnailService, 'getMimeTypeKey').and.returnValue('FAKE_TYPE'); - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); let imgEl = element.querySelector('#result_row_0 img'); expect(imgEl).not.toBeNull(); @@ -153,7 +153,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(noResult)); - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); expect(element.querySelector('#search_no_result')).not.toBeNull(); done(); @@ -176,7 +176,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should display an error if an error is encountered running the search', (done) => { - component.errorEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => {}, () => { fixture.detectChanges(); let resultsEl = element.querySelector('[data-automation-id="autocomplete_results"]'); let errorEl = element.querySelector('[data-automation-id="autocomplete_error_message"]'); @@ -213,19 +213,19 @@ describe('AlfrescoSearchAutocompleteComponent', () => { searchService = fixture.debugElement.injector.get(AlfrescoSearchService); }); - it('should emit preview when file item clicked', (done) => { + it('should emit file select when file item clicked', (done) => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(result)); - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).click(); }); updateSearchTerm('searchTerm'); - component.preview.subscribe(() => { + component.fileSelect.subscribe(() => { done(); }); }); @@ -235,11 +235,11 @@ describe('AlfrescoSearchAutocompleteComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(folderResult)); - spyOn(component.preview, 'emit'); - component.resultsEmitter.subscribe(() => { + spyOn(component.fileSelect, 'emit'); + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).click(); - expect(component.preview.emit).not.toHaveBeenCalled(); + expect(component.fileSelect.emit).not.toHaveBeenCalled(); done(); }); @@ -258,9 +258,9 @@ describe('AlfrescoSearchAutocompleteComponent', () => { .and.returnValue(Promise.resolve(results)); }); - it('should emit preview when enter key pressed when a file item is in focus', (done) => { + it('should emit file select when enter key pressed when a file item is in focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' @@ -269,14 +269,14 @@ describe('AlfrescoSearchAutocompleteComponent', () => { updateSearchTerm('searchTerm'); - component.preview.subscribe(() => { + component.fileSelect.subscribe(() => { done(); }); }); it('should emit cancel event when escape key pressed when a result is in focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' @@ -285,14 +285,14 @@ describe('AlfrescoSearchAutocompleteComponent', () => { updateSearchTerm('searchTerm'); - component.cancelEmitter.subscribe(() => { + component.cancel.subscribe(() => { done(); }); }); it('should focus the next result when down arrow key pressed when a result is in focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); let firstResult: any = element.querySelector('#result_row_0'); let secondResult: any = element.querySelector('#result_row_1'); @@ -310,7 +310,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should do nothing when down arrow key pressed when the last result is in focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); let lastResult: any = element.querySelector('#result_row_2'); lastResult.focus(); @@ -325,7 +325,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should focus the previous result when up arrow key pressed when a result is in focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); let firstResult: any = element.querySelector('#result_row_0'); let secondResult: any = element.querySelector('#result_row_1'); @@ -343,7 +343,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should emit scroll back event when up arrow key pressed and the first result is in focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); let firstResult: any = element.querySelector('#result_row_0'); firstResult.dispatchEvent(new KeyboardEvent('keyup', { @@ -351,7 +351,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { })); }); - component.scrollBackEmitter.subscribe(() => { + component.scrollBack.subscribe(() => { done(); }); @@ -372,14 +372,14 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should emit a focus event when a result comes into focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('focus')); }); updateSearchTerm('searchTerm'); - component.focusEmitter.subscribe((e: FocusEvent) => { + component.searchFocus.subscribe((e: FocusEvent) => { expect(e).not.toBeNull(); expect(e.type).toBe('focus'); done(); @@ -388,14 +388,14 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should emit a focus event when a result loses focus', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('blur')); }); updateSearchTerm('searchTerm'); - component.focusEmitter.subscribe((e: FocusEvent) => { + component.searchFocus.subscribe((e: FocusEvent) => { expect(e).not.toBeNull(); expect(e.type).toBe('blur'); done(); @@ -404,7 +404,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should give focus to the first result when focusResult() is called externally', (done) => { - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); let firstResult: any = element.querySelector('#result_row_0'); spyOn(firstResult, 'focus'); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index d0441a6aa9..bd409d59b2 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -44,22 +44,19 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { maxResults: number = 5; @Output() - preview: EventEmitter = new EventEmitter(); + fileSelect: EventEmitter = new EventEmitter(); @Output() - focusEmitter: EventEmitter = new EventEmitter(); + searchFocus: EventEmitter = new EventEmitter(); @Output() - cancelEmitter = new EventEmitter(); + cancel = new EventEmitter(); @Output() - resultsEmitter = new EventEmitter(); + resultsLoad = new EventEmitter(); @Output() - scrollBackEmitter = new EventEmitter(); - - @Output() - errorEmitter = new EventEmitter(); + scrollBack = new EventEmitter(); @ViewChild('resultsTableBody', {}) resultsTableBody: ElementRef; @@ -94,12 +91,12 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { results => { this.results = results.list.entries.slice(0, this.maxResults); this.errorMessage = null; - this.resultsEmitter.emit(this.results); + this.resultsLoad.emit(this.results); }, error => { this.results = null; this.errorMessage = error; - this.errorEmitter.emit(error); + this.resultsLoad.error(error); } ); } @@ -138,7 +135,7 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { onItemClick(node): void { if (node && node.entry) { if (node.entry.isFile) { - this.preview.emit({ + this.fileSelect.emit({ value: node }); } @@ -146,17 +143,17 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { } onRowFocus($event: FocusEvent): void { - this.focusEmitter.emit($event); + this.searchFocus.emit($event); } onRowBlur($event: FocusEvent): void { - this.focusEmitter.emit($event); + this.searchFocus.emit($event); } onRowEnter(node): void { if (node && node.entry) { if (node.entry.isFile) { - this.preview.emit({ + this.fileSelect.emit({ value: node }); } @@ -183,12 +180,12 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { if (previousElement) { ( previousElement).focus(); } else { - this.scrollBackEmitter.emit($event); + this.scrollBack.emit($event); } } onRowEscape($event: KeyboardEvent): void { - this.cancelEmitter.emit($event); + this.cancel.emit($event); } } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html index ab75efb3e5..2ea9201e75 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html @@ -24,7 +24,7 @@ + (fileSelect)="onFileClicked($event)" + (searchFocus)="onAutoCompleteFocus($event)" + (scrollBack)="onAutoCompleteReturn($event)" + (cancel)="onAutoCompleteCancel($event)"> diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts index dd7b0f1157..de1382c37c 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.spec.ts @@ -338,12 +338,12 @@ describe('AlfrescoSearchControlComponent', () => { describe('file preview', () => { - it('should emit a preview event when onFileClicked is called', () => { - spyOn(component.preview, 'emit'); + it('should emit a file select event when onFileClicked is called', () => { + spyOn(component.fileSelect, 'emit'); component.onFileClicked({ value: 'node12345' }); - expect(component.preview.emit).toHaveBeenCalledWith({ + expect(component.fileSelect.emit).toHaveBeenCalledWith({ 'value': 'node12345' }); }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts index 01f46edf71..0657cf121f 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts @@ -49,7 +49,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { searchSubmit = new EventEmitter(); @Output() - preview = new EventEmitter(); + fileSelect = new EventEmitter(); @Output() expand = new EventEmitter(); @@ -158,7 +158,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { } onFileClicked(event): void { - this.preview.emit({ + this.fileSelect.emit({ value: event.value }); } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts index b4278936c4..8489511c7d 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts @@ -162,7 +162,7 @@ describe('AlfrescoSearchComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(result)); - component.resultsEmitter.subscribe(x => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); expect(searchService.getSearchNodesPromise).toHaveBeenCalled(); expect(element.querySelector('#result_user_0')).not.toBeNull(); @@ -181,7 +181,7 @@ describe('AlfrescoSearchComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(noResult)); - component.resultsEmitter.subscribe(x => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); expect(element.querySelector('#search_no_result')).not.toBeNull(); done(); @@ -197,7 +197,7 @@ describe('AlfrescoSearchComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.reject(errorJson)); - component.errorEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => {}, () => { fixture.detectChanges(); let resultsEl = element.querySelector('[data-automation-id="search_result_table"]'); let errorEl = element.querySelector('[data-automation-id="search_error_message"]'); @@ -217,7 +217,7 @@ describe('AlfrescoSearchComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(result)); - component.resultsEmitter.subscribe(x => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); expect(searchService.getSearchNodesPromise).toHaveBeenCalledWith('searchTerm2'); expect(element.querySelector('#result_user_0')).not.toBeNull(); @@ -238,7 +238,7 @@ describe('AlfrescoSearchComponent', () => { spyOn(searchService, 'getSearchNodesPromise') .and.returnValue(Promise.resolve(result)); - component.resultsEmitter.subscribe(() => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).click(); }); @@ -246,7 +246,7 @@ describe('AlfrescoSearchComponent', () => { component.searchTerm = 'searchTerm'; component.ngOnInit(); - component.preview.subscribe(e => { + component.preview.subscribe(() => { done(); }); }); @@ -258,7 +258,7 @@ describe('AlfrescoSearchComponent', () => { .and.returnValue(Promise.resolve(folderResult)); spyOn(component.preview, 'emit'); - component.resultsEmitter.subscribe(x => { + component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).click(); expect(component.preview.emit).not.toHaveBeenCalled(); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts index e38bb3e761..cd71b4617a 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts @@ -38,10 +38,7 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { preview: EventEmitter = new EventEmitter(); @Output() - resultsEmitter = new EventEmitter(); - - @Output() - errorEmitter = new EventEmitter(); + resultsLoad = new EventEmitter(); results: any = null; @@ -112,13 +109,13 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { .subscribe( results => { this.results = results.list.entries; - this.resultsEmitter.emit(this.results); + this.resultsLoad.emit(this.results); this.errorMessage = null; }, error => { this.results = null; this.errorMessage = error; - this.errorEmitter.emit(error); + this.resultsLoad.error(error); } ); } From 88d21a4bbd78bc1eff3d1afe1fe1ae911bba9bd2 Mon Sep 17 00:00:00 2001 From: Will Abson Date: Mon, 31 Oct 2016 16:58:15 +0000 Subject: [PATCH 8/8] Avoid later cast through type in declarations --- .../alfresco-search-autocomplete.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index bd409d59b2..903883f98a 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -128,8 +128,8 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { } focusResult(): void { - let firstResult = this.resultsTableBody.nativeElement.querySelector('tr'); - ( firstResult).focus(); + let firstResult: any = this.resultsTableBody.nativeElement.querySelector('tr'); + firstResult.focus(); } onItemClick(node): void { @@ -169,16 +169,16 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { } onRowArrowDown($event: KeyboardEvent): void { - let nextElement = this.getNextElementSibling( $event.target); + let nextElement: any = this.getNextElementSibling( $event.target); if (nextElement) { - ( nextElement).focus(); + nextElement.focus(); } } onRowArrowUp($event: KeyboardEvent): void { - let previousElement = this.getPreviousElementSibling( $event.target); + let previousElement: any = this.getPreviousElementSibling( $event.target); if (previousElement) { - ( previousElement).focus(); + previousElement.focus(); } else { this.scrollBack.emit($event); }