Skip to content
Snippets Groups Projects
Commit b01b82e3 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-6129 improve test

parent 62eb8daf
No related branches found
No related tags found
No related merge requests found
import { getElementFromFixtureByType } from '@alfa-client/test-utils';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import { EventEmitter } from '@angular/core';
import {
ComponentFixture,
TestBed,
discardPeriodicTasks,
fakeAsync,
tick,
} from '@angular/core/testing';
import { Subscription } from 'rxjs';
import { SearchResultItemComponent } from '../search-result-item/search-result-item.component';
import { InstantSearchComponent } from './instant-search.component';
import { InstantSearchResult } from './instant-search.model';
import { InstantSearchQuery, InstantSearchResult } from './instant-search.model';
describe('InstantSearchComponent', () => {
let component: InstantSearchComponent;
......@@ -13,14 +19,23 @@ describe('InstantSearchComponent', () => {
{ title: 'test', description: 'test' },
{ title: 'caption', description: 'desc' },
];
const searchBy: string = 'query';
let searchQueryChanged: Mock<EventEmitter<any>>;
let searchResultSelected: Mock<EventEmitter<any>>;
beforeEach(async () => {
searchQueryChanged = <any>mock(EventEmitter);
searchResultSelected = <any>mock(EventEmitter);
await TestBed.configureTestingModule({
imports: [InstantSearchComponent],
}).compileComponents();
fixture = TestBed.createComponent(InstantSearchComponent);
component = fixture.componentInstance;
component.searchQueryChanged = useFromMock(searchQueryChanged);
component.searchResultSelected = useFromMock(searchResultSelected);
fixture.detectChanges();
});
......@@ -29,26 +44,110 @@ describe('InstantSearchComponent', () => {
});
describe('ngOnInit', () => {
// it('should call showResults after inputting 2 chars', fakeAsync(() => {
// component.showResults = jest.fn();
// const input: HTMLInputElement = getElementFromFixture(fixture, 'input');
it('should handle value changes', () => {
component.handleValueChanges = jest.fn();
// component.ngOnInit();
// input.value = 'Am';
// dispatchEventFromFixture(fixture, 'input', 'input');
// fixture.detectChanges();
// tick(400);
component.ngOnInit();
// expect(component.showResults).toHaveBeenCalledTimes(1);
// }));
expect(component.handleValueChanges).toHaveBeenCalled();
});
});
describe('handleValueChanges', () => {
beforeEach(() => {
component.showResults = jest.fn();
});
it('should subscribe to value changes', () => {
component.control.valueChanges.subscribe = jest.fn();
component.ngOnInit();
component.handleValueChanges();
expect(component.control.valueChanges.subscribe).toHaveBeenCalled();
});
it('should emit query', fakeAsync(() => {
component.handleValueChanges();
component.control.setValue(searchBy);
tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS);
component.control.valueChanges.subscribe();
expect(searchQueryChanged.emit).toHaveBeenCalledWith({ searchBy } as InstantSearchQuery);
}));
it('should not emit query', fakeAsync(() => {
component.handleValueChanges();
const searchBy: string = 'q';
component.control.setValue(searchBy);
tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS);
component.control.valueChanges.subscribe();
expect(searchQueryChanged.emit).not.toHaveBeenCalled();
}));
describe('result are already visible', () => {
beforeEach(() => {
component.areResultsVisible = true;
});
it('should not show results', fakeAsync(() => {
component.handleValueChanges();
component.control.setValue(searchBy);
tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS);
component.control.valueChanges.subscribe();
expect(component.showResults).not.toHaveBeenCalled();
discardPeriodicTasks();
}));
});
describe('results are not visible', () => {
beforeEach(() => {
component.areResultsVisible = false;
});
it('should show results', fakeAsync(() => {
component.handleValueChanges();
component.control.setValue(searchBy);
tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS);
component.control.valueChanges.subscribe();
expect(component.showResults).toHaveBeenCalled();
discardPeriodicTasks();
}));
it('should not show results if debounce time not reached', fakeAsync(() => {
component.handleValueChanges();
component.control.setValue(searchBy);
tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS - 1);
component.control.valueChanges.subscribe();
expect(component.showResults).not.toHaveBeenCalled();
discardPeriodicTasks();
}));
it('should not show results if not enough characters entered', fakeAsync(() => {
component.handleValueChanges();
component.control.setValue('q');
tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS);
component.control.valueChanges.subscribe();
expect(component.showResults).not.toHaveBeenCalled();
discardPeriodicTasks();
}));
});
});
describe('ngOnDestroy', () => {
......@@ -62,21 +161,6 @@ describe('InstantSearchComponent', () => {
});
});
describe('searchResultSelected', () => {
it('should emit event', () => {
const result: InstantSearchResult<unknown> = { title: 'test', description: 'test' };
component.searchResults = [result];
component.showResults();
fixture.detectChanges();
const element = getElementFromFixtureByType(fixture, SearchResultItemComponent);
const emitSpy = jest.spyOn(component.searchResultSelected, 'emit');
element.itemClicked.emit();
expect(emitSpy).toHaveBeenCalledWith(result);
});
});
describe('set searchResults', () => {
describe('on different results', () => {
it('should call setSearchResults', () => {
......@@ -132,6 +216,7 @@ describe('InstantSearchComponent', () => {
beforeEach(() => {
component.resultsRef.get = jest.fn().mockReturnValue({ setFocus: jest.fn() });
});
it('should call get for resultsRef with index', () => {
component.setFocusOnResultItem(1);
......@@ -300,7 +385,7 @@ describe('InstantSearchComponent', () => {
it('should set isShowResults to true', () => {
component.showResults();
expect(component.isShowResults).toBe(true);
expect(component.areResultsVisible).toBe(true);
});
});
......@@ -308,7 +393,7 @@ describe('InstantSearchComponent', () => {
it('should set isShowResults to false', () => {
component.hideResults();
expect(component.isShowResults).toBe(false);
expect(component.areResultsVisible).toBe(false);
});
});
......@@ -482,23 +567,21 @@ describe('InstantSearchComponent', () => {
});
});
describe('onClickItem', () => {
describe('onItemClicked', () => {
it('should emit searchResultSelected', () => {
component.searchResultSelected.emit = jest.fn();
component.onClickItem(searchResults[0], 0);
component.onItemClicked(searchResults[0], 0);
expect(component.searchResultSelected.emit).toHaveBeenCalledWith(searchResults[0]);
expect(searchResultSelected.emit).toHaveBeenCalledWith(searchResults[0]);
});
});
describe('onClickHandler', () => {
const e: MouseEvent = { ...new MouseEvent('test') };
beforeEach(() => {
component.hideResults = jest.fn();
});
const e: MouseEvent = { ...new MouseEvent('test') };
it('should call hideResults if instant search does not contain event target', () => {
component.onClickHandler(e);
......
......@@ -20,7 +20,7 @@ import { SearchFieldComponent } from '../search-field/search-field.component';
import { SearchResultHeaderComponent } from '../search-result-header/search-result-header.component';
import { SearchResultItemComponent } from '../search-result-item/search-result-item.component';
import { SearchResultLayerComponent } from '../search-result-layer/search-result-layer.component';
import { InstantSearchResult } from './instant-search.model';
import { InstantSearchQuery, InstantSearchResult } from './instant-search.model';
@Component({
selector: 'ods-instant-search',
......@@ -45,7 +45,7 @@ import { InstantSearchResult } from './instant-search.model';
/>
<ods-aria-live-region [text]="ariaLiveText" />
<ods-search-result-layer
*ngIf="results.length && isShowResults"
*ngIf="results.length && areResultsVisible"
class="absolute z-50 mt-3 w-full"
id="results"
>
......@@ -54,13 +54,15 @@ import { InstantSearchResult } from './instant-search.model';
*ngFor="let result of results; let i = index"
[title]="result.title"
[description]="result.description"
(itemClicked)="onClickItem(result, i)"
(itemClicked)="onItemClicked(result, i)"
#results
></ods-search-result-item>
</ods-search-result-layer>
</div>`,
})
export class InstantSearchComponent implements OnInit, OnDestroy {
static readonly DEBOUNCE_TIME_IN_MILLIS: number = 300;
@Input() label: string = EMPTY_STRING;
@Input() placeholder: string = EMPTY_STRING;
@Input() headerText: string = EMPTY_STRING;
......@@ -75,12 +77,14 @@ export class InstantSearchComponent implements OnInit, OnDestroy {
@Output() searchResultSelected: EventEmitter<InstantSearchResult<unknown>> = new EventEmitter<
InstantSearchResult<unknown>
>();
@Output() searchQueryChanged: EventEmitter<InstantSearchQuery> =
new EventEmitter<InstantSearchQuery>();
readonly FIRST_ITEM_INDEX: number = 0;
readonly PREVIEW_SEARCH_STRING_MIN_LENGTH: number = 2;
results: InstantSearchResult<unknown>[] = [];
ariaLiveText: string = '';
isShowResults: boolean = true;
areResultsVisible: boolean = true;
private focusedResult: number | undefined = undefined;
formControlSubscription: Subscription;
......@@ -89,15 +93,21 @@ export class InstantSearchComponent implements OnInit, OnDestroy {
@ViewChildren('results') resultsRef: QueryList<SearchResultItemComponent>;
ngOnInit(): void {
this.handleValueChanges();
}
handleValueChanges() {
this.formControlSubscription = this.control.valueChanges
.pipe(
debounceTime(300),
filter(() => !this.isShowResults),
debounceTime(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS),
filter((value: string) => value.length >= this.PREVIEW_SEARCH_STRING_MIN_LENGTH),
distinctUntilChanged(),
)
.subscribe(() => {
.subscribe((searchBy: string) => {
this.searchQueryChanged.emit({ searchBy });
if (!this.areResultsVisible) {
this.showResults();
}
});
}
......@@ -141,22 +151,14 @@ export class InstantSearchComponent implements OnInit, OnDestroy {
}
getNextResultIndex(index: number | undefined, resultLength: number): number {
if (isUndefined(index)) {
return this.FIRST_ITEM_INDEX;
}
if (this.isLastItemOrOutOfArray(index, resultLength)) {
return this.FIRST_ITEM_INDEX;
}
if (isUndefined(index)) return this.FIRST_ITEM_INDEX;
if (this.isLastItemOrOutOfArray(index, resultLength)) return this.FIRST_ITEM_INDEX;
return index + 1;
}
getPreviousResultIndex(index: number | undefined, resultLength: number): number {
if (isUndefined(index)) {
return this.getLastItemIndex(resultLength);
}
if (this.isFirstItemOrOutOfArray(index)) {
return this.getLastItemIndex(resultLength);
}
if (isUndefined(index)) return this.getLastItemIndex(resultLength);
if (this.isFirstItemOrOutOfArray(index)) return this.getLastItemIndex(resultLength);
return index - 1;
}
......@@ -185,12 +187,12 @@ export class InstantSearchComponent implements OnInit, OnDestroy {
}
showResults(): void {
this.isShowResults = true;
this.areResultsVisible = true;
this.focusedResult = undefined;
}
hideResults(): void {
this.isShowResults = false;
this.areResultsVisible = false;
this.focusedResult = undefined;
}
......@@ -214,8 +216,8 @@ export class InstantSearchComponent implements OnInit, OnDestroy {
return e.key === 'Escape';
}
onClickItem(result: InstantSearchResult<unknown>, index: number) {
this.searchResultSelected.emit(result);
onItemClicked(searchResult: InstantSearchResult<unknown>, index: number) {
this.searchResultSelected.emit(searchResult);
this.focusedResult = index;
}
}
......@@ -3,3 +3,7 @@ export interface InstantSearchResult<T> {
description: string;
data?: T;
}
export interface InstantSearchQuery {
searchBy: string;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment