90918: Fix RxJs issues

This commit is contained in:
Yura Bondarenko
2022-04-25 15:23:11 +02:00
parent 8d6f156db1
commit 809072e86a
11 changed files with 66 additions and 25 deletions

View File

@@ -3,7 +3,7 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock';
import { RequestService } from '../data/request.service';
import { HALEndpointService } from './hal-endpoint.service';
import { EndpointMapRequest } from '../data/request.models';
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { environment } from '../../../environments/environment';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
@@ -162,9 +162,9 @@ describe('HALEndpointService', () => {
return observableOf(endpointMaps[param]);
});
observableCombineLatest([
observableCombineLatest<string[]>([
(service as any).getEndpointAt(start, 'one'),
(service as any).getEndpointAt(start, 'one', 'two')
(service as any).getEndpointAt(start, 'one', 'two'),
]).subscribe(([endpoint1, endpoint2]) => {
expect(endpoint1).toEqual(one);
expect(endpoint2).toEqual(two);

View File

@@ -1,5 +1,5 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { debounceTime, filter, find, map, switchMap, take, takeWhile } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, Observable, interval } from 'rxjs';
import { filter, find, map, switchMap, take, takeWhile, debounce, debounceTime } from 'rxjs/operators';
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
import { SearchResult } from '../../shared/search/models/search-result.model';
import { PaginatedList } from '../data/paginated-list.model';
@@ -9,6 +9,17 @@ import { MetadataSchema } from '../metadata/metadata-schema.model';
import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model';
import { InjectionToken } from '@angular/core';
import { MonoTypeOperatorFunction, SchedulerLike } from 'rxjs/internal/types';
/**
* Use this method instead of the RxJs debounceTime if you're waiting for debouncing in tests;
* debounceTime doesn't work with fakeAsync/tick anymore as of Angular 13.2.6 & RxJs 7.5.5
* Workaround suggested in https://github.com/angular/angular/issues/44351#issuecomment-1107454054
* todo: remove once the above issue is fixed
*/
export const debounceTimeWorkaround = <T>(dueTime: number, scheduler?: SchedulerLike): MonoTypeOperatorFunction<T> => {
return debounce(() => interval(dueTime, scheduler));
};
export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<<T>(dueTime: number) => (source: Observable<T>) => Observable<T>>('debounceTime', {
providedIn: 'root',

View File

@@ -5,8 +5,9 @@ import { FormGroup } from '@angular/forms';
import { hasValue, isEmpty } from '../../shared/empty.util';
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { debounceTime, map } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { debounceTimeWorkaround as debounceTime } from '../../core/shared/operators';
@Component({
selector: 'ds-profile-page-security-form',

View File

@@ -143,7 +143,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
this.typesString = this.types.map((type: string) => type.toString().toLowerCase()).join(', ');
// Create an observable searching for the current DSO (return empty list if there's no current DSO)
let currentDSOResult$;
let currentDSOResult$: Observable<PaginatedList<SearchResult<DSpaceObject>>>;
if (isNotEmpty(this.currentDSOId)) {
currentDSOResult$ = this.search(this.getCurrentDSOQuery(), 1).pipe(getFirstSucceededRemoteDataPayload());
} else {

View File

@@ -100,7 +100,9 @@ describe('ItemVersionsComponent', () => {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', ['isAuthorized']);
const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true)
});
const workspaceItemDataServiceSpy = jasmine.createSpyObj('workspaceItemDataService', {
findByItem: EMPTY,
});

View File

@@ -61,7 +61,7 @@ export function createFailedRemoteDataObject<T>(errorMessage?: string, statusCod
* @param timeCompleted the moment when the remoteData was completed
*/
export function createFailedRemoteDataObject$<T>(errorMessage?: string, statusCode?: number, timeCompleted?: number): Observable<RemoteData<T>> {
return observableOf(createFailedRemoteDataObject(errorMessage, statusCode, timeCompleted));
return observableOf(createFailedRemoteDataObject<T>(errorMessage, statusCode, timeCompleted));
}
/**
@@ -85,7 +85,7 @@ export function createPendingRemoteDataObject<T>(lastVerified = FIXED_TIMESTAMP)
* @param lastVerified the moment when the remoteData was last verified
*/
export function createPendingRemoteDataObject$<T>(lastVerified?: number): Observable<RemoteData<T>> {
return observableOf(createPendingRemoteDataObject(lastVerified));
return observableOf(createPendingRemoteDataObject<T>(lastVerified));
}
/**

View File

@@ -80,7 +80,7 @@ describe('SearchFiltersComponent', () => {
expect(comp.initFilters).toHaveBeenCalledTimes(1);
refreshFiltersEmitter.next();
refreshFiltersEmitter.next(null);
expect(comp.initFilters).toHaveBeenCalledTimes(2);
});

View File

@@ -38,7 +38,7 @@ export class VocabularyTreeFlatDataSource<T, F> extends DataSource<F> {
this._treeControl.expansionModel.changed,
this._flattenedData
];
return merge(...changes).pipe(map(() => {
return merge<any>(...changes).pipe(map((): F[] => {
this._expandedData.next(
this._treeFlattener.expandFlattenedNodes(this._flattenedData.value, this._treeControl));
return this._expandedData.value;

View File

@@ -2,7 +2,7 @@ import { TestBed, waitForAsync } from '@angular/core/testing';
import { TestScheduler } from 'rxjs/testing';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { getTestScheduler, hot } from 'jasmine-marbles';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { VocabularyTreeviewService } from './vocabulary-treeview.service';
import { VocabularyService } from '../../core/submission/vocabularies/vocabulary.service';
@@ -14,6 +14,8 @@ import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models
import { buildPaginatedList } from '../../core/data/paginated-list.model';
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model';
import { expand, map, switchMap } from 'rxjs/operators';
import { from as observableFrom } from 'rxjs';
describe('VocabularyTreeviewService test suite', () => {
@@ -320,10 +322,25 @@ describe('VocabularyTreeviewService test suite', () => {
scheduler.schedule(() => service.searchByQuery(vocabularyOptions));
scheduler.flush();
searchChildNode.childrenChange.next([searchChildNode3]);
searchItemNode.childrenChange.next([searchChildNode]);
expect(serviceAsAny.dataChange.value.length).toEqual(1);
expect(serviceAsAny.dataChange.value).toEqual([searchItemNode]);
// We can't check the tree by comparing root TreeviewNodes directly in this particular test;
// Since RxJs 7, BehaviorSubjects can no longer be reliably compared because of the new currentObservers property
// (see https://github.com/ReactiveX/rxjs/pull/6842)
const levels$ = serviceAsAny.dataChange.pipe(
expand((nodes: TreeviewNode[]) => { // recursively apply:
return observableFrom(nodes).pipe( // for each node in the array...
switchMap(node => node.childrenChange) // ...map it to the array its child nodes.
); // because we only have one child per node in this case,
}), // this results in an array of nodes for each level of the tree.
map((nodes: TreeviewNode[]) => nodes.map(node => node.item)), // finally, replace nodes with their vocab entries
);
// Confirm that this corresponds to the hierarchy we set up above
expect(levels$).toBeObservable(cold('-(abcd)', {
a: [item],
b: [child],
c: [child3],
d: [] // ensure that grandchild has no children & the recursion stopped there
}));
});
});

View File

@@ -64,14 +64,24 @@ describe('SubmissionImportExternalCollectionComponent test suite', () => {
compAsAny = null;
});
it('The variable \'selectedEvent\' should be assigned', () => {
const event = new EventEmitter<CollectionListEntry>();
comp.selectObject(event);
it('should emit from selectedEvent on selectObject', () => {
spyOn(comp.selectedEvent, 'emit').and.callThrough();
expect(comp.selectedEvent).toEqual(event);
const entry = {
communities: [
{ id: 'community1' },
{ id: 'community2' }
],
collection: {
id: 'collection'
}
} as CollectionListEntry;
comp.selectObject(entry);
expect(comp.selectedEvent.emit).toHaveBeenCalledWith(entry);
});
it('The variable \'selectedEvent\' should be assigned', () => {
it('should dismiss modal on closeCollectionModal', () => {
spyOn(compAsAny.activeModal, 'dismiss');
comp.closeCollectionModal();

View File

@@ -35,10 +35,10 @@ export class SubmissionImportExternalCollectionComponent {
) { }
/**
* This method populates the 'selectedEvent' variable.
* This method emits the selected Collection from the 'selectedEvent' variable.
*/
public selectObject(event): void {
this.selectedEvent.emit(event);
public selectObject(object: CollectionListEntry): void {
this.selectedEvent.emit(object);
}
/**