mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[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:
committed by
Denys Vuika
parent
ff0891009e
commit
dcacbc1210
@@ -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);
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
@@ -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: [] } };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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 => {
|
||||
|
Reference in New Issue
Block a user