[ACA-19] show search libraries hint (#802)

* [ACA-19] show hint on 400 error

* [ACA-19] unit test

* [ACA-19] small change

* [ACA-19] unit test

* [ACA-19] unit tests

* [ACA-19] avoid memory leaks with takeUntil

* [ACA-19] remove comment & formatting

* [ACA-19] update documentation
This commit is contained in:
Suzana Dirla
2018-11-16 14:35:41 +02:00
committed by Denys Vuika
parent ff0891009e
commit dcacbc1210
13 changed files with 343 additions and 24 deletions

View File

@@ -24,9 +24,73 @@
*/
import { SearchInputControlComponent } from './search-input-control.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('SearchInputControlComponent', () => {
it('should be defined', () => {
expect(SearchInputControlComponent).toBeDefined();
let fixture: ComponentFixture<SearchInputControlComponent>;
let component: SearchInputControlComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule],
declarations: [SearchInputControlComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(SearchInputControlComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('should emit submit event on searchSubmit', async () => {
const keyboardEvent = { target: { value: 'a' } };
let eventArgs = null;
component.submit.subscribe(args => (eventArgs = args));
await component.searchSubmit(keyboardEvent);
expect(eventArgs).toBe(keyboardEvent);
});
it('should emit searchChange event on inputChange', async () => {
const searchTerm = 'b';
let eventArgs = null;
component.searchChange.subscribe(args => (eventArgs = args));
await component.inputChange(searchTerm);
expect(eventArgs).toBe(searchTerm);
});
it('should emit searchChange event on clear', async () => {
let eventArgs = null;
component.searchChange.subscribe(args => (eventArgs = args));
await component.clear();
expect(eventArgs).toBe('');
});
it('should clear searchTerm', async () => {
component.searchTerm = 'c';
fixture.detectChanges();
await component.clear();
expect(component.searchTerm).toBe('');
});
it('should check if searchTerm has a length less than 2', () => {
expect(component.isTermTooShort()).toBe(false);
component.searchTerm = 'd';
fixture.detectChanges();
expect(component.isTermTooShort()).toBe(true);
component.searchTerm = 'dd';
fixture.detectChanges();
expect(component.isTermTooShort()).toBe(false);
});
});

View File

@@ -87,8 +87,6 @@ export class SearchInputControlComponent implements OnDestroy {
}
isTermTooShort() {
const alphanumericTerm = this.searchTerm.replace(/[^0-9a-z]/gi, '');
return this.searchTerm.length && alphanumericTerm.length < 2;
return !!(this.searchTerm && this.searchTerm.length < 2);
}
}

View File

@@ -39,11 +39,13 @@ import { SEARCH_BY_TERM, SearchByTermAction } from '../../../store/actions';
import { map } from 'rxjs/operators';
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
import { ContentManagementService } from '../../../services/content-management.service';
describe('SearchInputComponent', () => {
let fixture: ComponentFixture<SearchInputComponent>;
let component: SearchInputComponent;
let actions$: Actions;
let content: ContentManagementService;
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -56,11 +58,33 @@ describe('SearchInputComponent', () => {
.then(() => {
actions$ = TestBed.get(Actions);
fixture = TestBed.createComponent(SearchInputComponent);
content = TestBed.get(ContentManagementService);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('should change flag on library400Error event', () => {
expect(component.has400LibraryError).toBe(false);
content.library400Error.next();
expect(component.has400LibraryError).toBe(true);
});
it('should have no library constraint by default', () => {
expect(component.hasLibraryConstraint()).toBe(false);
});
it('should have library constraint on 400 error received', () => {
const libItem = component.searchOptions.find(
item => item.key.toLowerCase().indexOf('libraries') > 0
);
libItem.value = true;
content.library400Error.next();
expect(component.hasLibraryConstraint()).toBe(true);
});
describe('onSearchSubmit()', () => {
it('should call search action with correct search options', fakeAsync(done => {
const searchedTerm = 's';

View File

@@ -23,7 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import {
Component,
OnDestroy,
OnInit,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import {
NavigationEnd,
PRIMARY_OUTLET,
@@ -37,9 +43,11 @@ import { SearchInputControlComponent } from '../search-input-control/search-inpu
import { Store } from '@ngrx/store';
import { AppStore } from '../../../store/states/app.state';
import { SearchByTermAction } from '../../../store/actions';
import { filter } from 'rxjs/operators';
import { filter, takeUntil } from 'rxjs/operators';
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../../services/content-management.service';
import { Subject } from 'rxjs';
export enum SearchOptionIds {
Files = 'files',
@@ -53,10 +61,12 @@ export enum SearchOptionIds {
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-search-input' }
})
export class SearchInputComponent implements OnInit {
export class SearchInputComponent implements OnInit, OnDestroy {
onDestroy$: Subject<boolean> = new Subject<boolean>();
hasOneChange = false;
hasNewChange = false;
navigationTimer: any;
has400LibraryError = false;
searchedWord = null;
searchOptions: Array<any> = [
@@ -86,6 +96,7 @@ export class SearchInputComponent implements OnInit {
constructor(
private librariesQueryBuilder: SearchLibrariesQueryBuilderService,
private queryBuilder: SearchQueryBuilderService,
private content: ContentManagementService,
private router: Router,
private store: Store<AppStore>
) {}
@@ -94,15 +105,23 @@ export class SearchInputComponent implements OnInit {
this.showInputValue();
this.router.events
.pipe(takeUntil(this.onDestroy$))
.pipe(filter(e => e instanceof RouterEvent))
.subscribe(event => {
if (event instanceof NavigationEnd) {
this.showInputValue();
}
});
this.content.library400Error
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => {
this.has400LibraryError = true;
});
}
showInputValue() {
this.has400LibraryError = false;
this.searchedWord = '';
if (this.onSearchResults || this.onLibrariesSearchResults) {
@@ -121,12 +140,18 @@ export class SearchInputComponent implements OnInit {
}
}
ngOnDestroy(): void {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
/**
* Called when the user submits the search, e.g. hits enter or clicks submit
*
* @param event Parameters relating to the search
*/
onSearchSubmit(event: KeyboardEvent) {
this.has400LibraryError = false;
const searchTerm = (event.target as HTMLInputElement).value;
if (searchTerm) {
this.store.dispatch(
@@ -136,6 +161,7 @@ export class SearchInputComponent implements OnInit {
}
onSearchChange(searchTerm: string) {
this.has400LibraryError = false;
if (this.hasOneChange) {
this.hasNewChange = true;
} else {
@@ -158,6 +184,7 @@ export class SearchInputComponent implements OnInit {
}
onOptionChange() {
this.has400LibraryError = false;
if (this.searchedWord) {
if (this.isLibrariesChecked()) {
if (this.onLibrariesSearchResults) {
@@ -213,7 +240,9 @@ export class SearchInputComponent implements OnInit {
hasLibraryConstraint(): boolean {
if (this.isLibrariesChecked()) {
return this.searchInputControl.isTermTooShort();
return (
this.has400LibraryError || this.searchInputControl.isTermTooShort()
);
}
return false;
}

View File

@@ -93,4 +93,18 @@ describe('SearchLibrariesQueryBuilderService', () => {
const compiled = builder.buildQuery();
expect(compiled.opts).toEqual({ maxItems: 5, skipCount: 5 });
});
it('should raise an event on error', async () => {
const err = '{"error": {"statusCode": 400}}';
spyOn(queriesApi, 'findSites').and.returnValue(Promise.reject(err));
const query = {};
spyOn(builder, 'buildQuery').and.returnValue(query);
let eventArgs = null;
builder.hadError.subscribe(args => (eventArgs = args));
await builder.execute();
expect(eventArgs).toBe(err);
});
});

View File

@@ -36,6 +36,7 @@ export class SearchLibrariesQueryBuilderService {
updated: Subject<any> = new Subject();
executed: Subject<any> = new Subject();
hadError: Subject<any> = new Subject();
paging: { maxItems?: number; skipCount?: number } = null;
@@ -77,10 +78,13 @@ export class SearchLibrariesQueryBuilderService {
return null;
}
private findLibraries(libraryQuery: { term; opts }): Promise<SitePaging> {
private findLibraries(libraryQuery): Promise<SitePaging> {
return this.alfrescoApiService
.getInstance()
.core.queriesApi.findSites(libraryQuery.term, libraryQuery.opts)
.catch(() => ({ list: { pagination: { totalItems: 0 }, entries: [] } }));
.catch(err => {
this.hadError.next(err);
return { list: { pagination: { totalItems: 0 }, entries: [] } };
});
}
}

View File

@@ -93,6 +93,17 @@ export class SearchLibrariesResultsComponent extends PageComponent
this.isLoading = false;
}),
this.librariesQueryBuilder.hadError.subscribe(err => {
try {
const {
error: { statusCode }
} = JSON.parse(err.message);
if (statusCode === 400) {
this.content.library400Error.next();
}
} catch (e) {}
}),
this.breakpointObserver
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
.subscribe(result => {