mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 396: #1193 Trefferliste incl. Unit Tests
Related work items: #1193
This commit is contained in:
@@ -83,4 +83,6 @@ describe('CustomerSearch', () => {
|
||||
|
||||
describe('addLoadingProducts', () => {});
|
||||
describe('removeLoadingProducts', () => {});
|
||||
|
||||
describe('shouldFetchNewProducts', () => {});
|
||||
});
|
||||
|
||||
@@ -135,11 +135,13 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
|
||||
return this.search();
|
||||
}
|
||||
|
||||
search(options: { isNewSearch: boolean } = { isNewSearch: true }): void {
|
||||
if (
|
||||
!options.isNewSearch &&
|
||||
this.numberOfResultsFetched >= this.totalResults
|
||||
) {
|
||||
search(
|
||||
options: { isNewSearch: boolean; take?: number } = {
|
||||
isNewSearch: true,
|
||||
take: 10,
|
||||
}
|
||||
): void {
|
||||
if (!this.shouldFetchNewProducts(options)) {
|
||||
return; // early exit because no new products need to be fetched
|
||||
}
|
||||
if (this.searchState !== 'fetching') {
|
||||
@@ -150,10 +152,10 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
|
||||
take(1),
|
||||
tap((result) => {
|
||||
const effectiveTake = options.isNewSearch
|
||||
? 10
|
||||
: this.numberOfResultsFetched + 10 > result.hits
|
||||
? options.take
|
||||
: this.numberOfResultsFetched + options.take > result.hits
|
||||
? result.hits - this.numberOfResultsFetched
|
||||
: 10;
|
||||
: options.take;
|
||||
|
||||
this.searchResult$.next(
|
||||
this.addLoadingProducts(this.searchResult, effectiveTake)
|
||||
@@ -163,7 +165,7 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
|
||||
return this.customerSearch
|
||||
.getCustomers(this.queryFilter.query, {
|
||||
skip: options.isNewSearch ? 0 : this.numberOfResultsFetched,
|
||||
take: 10,
|
||||
take: options.take,
|
||||
})
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
@@ -196,7 +198,6 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
|
||||
...r,
|
||||
result: r.result.map((a) => ({ ...a, loaded: true })),
|
||||
});
|
||||
|
||||
if (this.searchState === 'result') {
|
||||
if (r.hits === 1) {
|
||||
this.navigateToDetails(r.result[0].id);
|
||||
@@ -285,4 +286,19 @@ export abstract class CustomerSearch implements OnInit, OnDestroy {
|
||||
result: currentResult.result.filter((r) => !!r.loaded),
|
||||
};
|
||||
}
|
||||
|
||||
private shouldFetchNewProducts(options: {
|
||||
isNewSearch: boolean;
|
||||
take?: number;
|
||||
}): boolean {
|
||||
if (options.isNewSearch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.totalResults > 0) {
|
||||
return !(this.numberOfResultsFetched + options.take >= this.totalResults);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
} from '@angular/core';
|
||||
import { Filter } from '@ui/filter';
|
||||
import { CustomerSearch } from '../customer-search.service';
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { forwardRef } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { PagedResult } from '@domain/defs';
|
||||
import { CustomerInfoDTO } from '@swagger/crm';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import {
|
||||
UiSearchboxAutocompleteComponent,
|
||||
UiSearchboxModule,
|
||||
} from '@ui/searchbox';
|
||||
import { UiSearchboxModule } from '@ui/searchbox';
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { CustomerSearchComponent } from '../customer-search.component';
|
||||
import { CustomerSearch } from '../customer-search.service';
|
||||
import { CustomerSearchMainComponent } from './search-main.component';
|
||||
@@ -20,18 +14,11 @@ import { CustomerSearchMainComponent } from './search-main.component';
|
||||
describe('CustomerSearchMainComponent', () => {
|
||||
let fixture: ComponentFixture<CustomerSearchMainComponent>;
|
||||
let component: CustomerSearchMainComponent;
|
||||
let autocompleteComponent: UiSearchboxAutocompleteComponent;
|
||||
|
||||
let searchService: jasmine.SpyObj<CustomerSearch>;
|
||||
let envService: jasmine.SpyObj<EnvironmentService>;
|
||||
|
||||
const result = [{ id: 'test 1' }, { id: 'test 2' }];
|
||||
const pagedResult: PagedResult<CustomerInfoDTO> = {
|
||||
hits: 2,
|
||||
result: [{ userName: '1' }, { userName: '2' }],
|
||||
};
|
||||
const mockActivatedRoute = ({
|
||||
snapshot: { queryParams: { query: 'test' } },
|
||||
} as unknown) as ActivatedRoute;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -45,30 +32,31 @@ describe('CustomerSearchMainComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
CustomerSearchComponent,
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: jasmine.createSpyObj('environmentService', ['isMobile']),
|
||||
},
|
||||
{
|
||||
provide: CustomerSearch,
|
||||
useExisting: forwardRef(() => CustomerSearchComponent),
|
||||
},
|
||||
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CustomerSearchMainComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
autocompleteComponent = TestBed.createComponent(
|
||||
UiSearchboxAutocompleteComponent
|
||||
).componentInstance;
|
||||
component.autocomplete = autocompleteComponent;
|
||||
|
||||
component.search.queryControl = new FormControl('');
|
||||
searchService = TestBed.inject(CustomerSearch) as jasmine.SpyObj<
|
||||
CustomerSearch
|
||||
>;
|
||||
|
||||
searchService.autocompleteResult$ = of(result);
|
||||
searchService.searchState$ = new BehaviorSubject('init');
|
||||
searchService.searchResult$ = new BehaviorSubject(pagedResult);
|
||||
|
||||
envService = TestBed.inject(EnvironmentService) as jasmine.SpyObj<
|
||||
EnvironmentService
|
||||
>;
|
||||
envService.isMobile.and.returnValue(Promise.resolve(true));
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@@ -76,90 +64,21 @@ describe('CustomerSearchMainComponent', () => {
|
||||
expect(component instanceof CustomerSearchMainComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('initAutocomplete', () => {
|
||||
describe('detectDevice', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'detectDevice').and.callThrough();
|
||||
fixture.detectChanges();
|
||||
spyOn(component, 'initAutocomplete').and.callThrough();
|
||||
spyOn(component.autocomplete, 'open').and.callFake(() => {});
|
||||
spyOn(component.autocomplete, 'close').and.callFake(() => {});
|
||||
});
|
||||
|
||||
it('should be called OnInit', async () => {
|
||||
component.ngOnInit();
|
||||
|
||||
await searchService.autocompleteResult$.pipe(take(1)).toPromise();
|
||||
|
||||
expect(component.initAutocomplete).toHaveBeenCalledTimes(1);
|
||||
expect(component.detectDevice).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should open the autocomplete results', async () => {
|
||||
component.ngOnInit();
|
||||
it('should set isMobile', async () => {
|
||||
expect(envService.isMobile).toHaveBeenCalled();
|
||||
|
||||
await searchService.autocompleteResult$.pipe(take(1)).toPromise();
|
||||
|
||||
expect(component.autocomplete.open).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should close the autocomplete results', async () => {
|
||||
searchService.autocompleteResult$ = of([]);
|
||||
component.ngOnInit();
|
||||
|
||||
await searchService.autocompleteResult$.pipe(take(1)).toPromise();
|
||||
|
||||
expect(component.autocomplete.close).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseQueryParams', () => {
|
||||
it('should be called onInit', () => {
|
||||
spyOn(component, 'initAutocomplete').and.callFake(() => {});
|
||||
spyOn(component, 'subscribeToSearchResult').and.callFake(() => {});
|
||||
spyOn(component.search, 'parseQueryParams').and.callFake(() => {});
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.search.parseQueryParams).toHaveBeenCalledWith(
|
||||
mockActivatedRoute
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribeToSearchResult', () => {
|
||||
beforeEach(() => {
|
||||
searchService.searchState$ = new BehaviorSubject('result');
|
||||
});
|
||||
|
||||
it('should be called OnInit', () => {
|
||||
spyOn(component, 'initAutocomplete').and.callFake(() => {});
|
||||
spyOn(component.search, 'parseQueryParams').and.callFake(() => {});
|
||||
spyOn(component, 'subscribeToSearchResult').and.callFake(() => {});
|
||||
|
||||
component.ngOnInit();
|
||||
expect(component.subscribeToSearchResult).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should navigate to results if more than 1 result exists', async () => {
|
||||
spyOn(component.search, 'navigateToResults').and.callFake(() => {});
|
||||
|
||||
component.subscribeToSearchResult();
|
||||
await component.search.searchResult$.pipe(take(1)).toPromise();
|
||||
|
||||
expect(component.search.navigateToResults).toHaveBeenCalled();
|
||||
});
|
||||
it('should navigate to details if 1 result exists', async () => {
|
||||
const singleResult: PagedResult<CustomerInfoDTO> = {
|
||||
hits: 1,
|
||||
result: [{ userName: '1', id: 1 }],
|
||||
};
|
||||
searchService.searchResult$ = new BehaviorSubject(singleResult);
|
||||
spyOn(component.search, 'navigateToDetails').and.callFake(() => {});
|
||||
|
||||
component.subscribeToSearchResult();
|
||||
await component.search.searchResult$.pipe(take(1)).toPromise();
|
||||
|
||||
expect(component.search.navigateToDetails).toHaveBeenCalledWith(
|
||||
singleResult.result[0].id
|
||||
);
|
||||
await envService.isMobile();
|
||||
expect(component['isMobile']).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { UiSearchboxAutocompleteComponent } from '@ui/searchbox';
|
||||
import { Subject } from 'rxjs';
|
||||
import { delay, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { CustomerSearch } from '../customer-search.service';
|
||||
|
||||
@Component({
|
||||
@@ -20,7 +23,6 @@ export class CustomerSearchMainComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
public search: CustomerSearch,
|
||||
public cdr: ChangeDetectorRef,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
public environmentService: EnvironmentService
|
||||
) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { CustomerSearchType } from '../../defs';
|
||||
import { CustomerResultCardComponent } from './customer-result-card.component';
|
||||
|
||||
describe('CustomerResultCardComponent', () => {
|
||||
let fixture: ComponentFixture<CustomerResultCardComponent>;
|
||||
let component: CustomerResultCardComponent;
|
||||
|
||||
const mockCustomer: CustomerSearchType = {
|
||||
loaded: true,
|
||||
created: new Date().toISOString(),
|
||||
customerType: 8,
|
||||
firstName: 'Unit',
|
||||
lastName: 'Test',
|
||||
communicationDetails: {
|
||||
email: 'unit@test.de',
|
||||
},
|
||||
features: [{ enabled: true, description: 'Great Feature' }],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CustomerResultCardComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CustomerResultCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
component.customer = mockCustomer;
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component instanceof CustomerResultCardComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show the customers name', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const name: HTMLSpanElement = fixture.debugElement.query(
|
||||
By.css('.heading > span')
|
||||
).nativeElement;
|
||||
|
||||
expect(name.textContent).toContain(mockCustomer.firstName);
|
||||
expect(name.textContent).toContain(mockCustomer.lastName);
|
||||
});
|
||||
|
||||
it('should show the customers created date', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const createdDate: HTMLSpanElement = fixture.debugElement.query(
|
||||
By.css('.date')
|
||||
).nativeElement;
|
||||
|
||||
expect(createdDate.textContent).toContain(
|
||||
String(new Date(mockCustomer.created).getMonth() + 1)
|
||||
);
|
||||
expect(createdDate.textContent).toContain(
|
||||
String(new Date(mockCustomer.created).getDay())
|
||||
);
|
||||
});
|
||||
|
||||
it('should show thecustomers features', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const features: HTMLDivElement[] = fixture.debugElement
|
||||
.queryAll(By.css('.feature'))
|
||||
.map((db) => db.nativeElement);
|
||||
|
||||
const firstFeatures: HTMLSpanElement = fixture.debugElement.query(
|
||||
By.css('.feature > span ')
|
||||
).nativeElement;
|
||||
|
||||
expect(features.length).toEqual(mockCustomer.features.length);
|
||||
expect(firstFeatures.textContent).toContain(
|
||||
mockCustomer.features[0].description
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { PagedResult } from 'apps/domain/defs/src/lib/models';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { CustomerSearchModule } from '../customer-search.module';
|
||||
import { CustomerSearch } from '../customer-search.service';
|
||||
import { CustomerSearchType, ResultState } from '../defs';
|
||||
import { CustomerSearchResultComponent } from './search-results.component';
|
||||
|
||||
class MockCustomerSearch {
|
||||
searchState$ = new BehaviorSubject<ResultState>('init');
|
||||
searchResult$ = new BehaviorSubject<PagedResult<CustomerSearchType>>({
|
||||
result: [{} as CustomerSearchType, {} as CustomerSearchType],
|
||||
});
|
||||
|
||||
search(options?: { isNewSearch: boolean; take?: number }) {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
fdescribe('CustomerSearchResultComponent', () => {
|
||||
let fixture: ComponentFixture<CustomerSearchResultComponent>;
|
||||
let component: CustomerSearchResultComponent;
|
||||
|
||||
let searchService: CustomerSearch;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CustomerSearchModule],
|
||||
providers: [
|
||||
{
|
||||
provide: CustomerSearch,
|
||||
useClass: MockCustomerSearch,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CustomerSearchResultComponent);
|
||||
component = fixture.componentInstance;
|
||||
searchService = TestBed.inject(CustomerSearch);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component instanceof CustomerSearchResultComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('checkIfReload', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'triggerSearch').and.callThrough();
|
||||
spyOn(component, 'checkIfReload').and.callThrough();
|
||||
});
|
||||
it('should be called on viewportEntered Event', () => {
|
||||
fixture.detectChanges();
|
||||
const resultCard = fixture.debugElement.query(By.css('a'));
|
||||
const target: HTMLAnchorElement = resultCard.nativeElement;
|
||||
|
||||
target.classList.add('last');
|
||||
|
||||
resultCard.triggerEventHandler('viewportEntered', target);
|
||||
|
||||
expect(component.checkIfReload).toHaveBeenCalledWith(target);
|
||||
expect(component.triggerSearch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call triggerSearch if the target element does not include the last class', () => {
|
||||
fixture.detectChanges();
|
||||
const resultCard = fixture.debugElement.query(By.css('a'));
|
||||
const target: HTMLAnchorElement = resultCard.nativeElement;
|
||||
|
||||
resultCard.triggerEventHandler('viewportEntered', target);
|
||||
|
||||
expect(component.triggerSearch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('triggerSearch', () => {
|
||||
it('should call search on the SearchService', () => {
|
||||
spyOn(searchService, 'search').and.callThrough();
|
||||
|
||||
component.triggerSearch();
|
||||
|
||||
expect(searchService.search).toHaveBeenCalledWith({
|
||||
isNewSearch: false,
|
||||
take: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -31,6 +31,6 @@ export class CustomerSearchResultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
triggerSearch() {
|
||||
this.search.search({ isNewSearch: false });
|
||||
this.search.search({ isNewSearch: false, take: 10 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user