Merge pull request #1104 from atmire/Angular-10-upgrade-test-fixes

Angular 10 upgrade test fixes
This commit is contained in:
Tim Donohue
2021-04-27 14:23:52 -05:00
committed by GitHub
16 changed files with 208 additions and 181 deletions

View File

@@ -1,4 +1,6 @@
import { hasNoValue } from '../../shared/empty.util'; import { hasNoValue } from '../../shared/empty.util';
import { InjectionToken } from '@angular/core';
import { GenericConstructor } from '../../core/shared/generic-constructor';
export enum BrowseByType { export enum BrowseByType {
Title = 'title', Title = 'title',
@@ -8,6 +10,11 @@ export enum BrowseByType {
export const DEFAULT_BROWSE_BY_TYPE = BrowseByType.Metadata; export const DEFAULT_BROWSE_BY_TYPE = BrowseByType.Metadata;
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
providedIn: 'root',
factory: () => getComponentByBrowseByType
});
const map = new Map(); const map = new Map();
/** /**

View File

@@ -2,12 +2,11 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import * as decorator from './browse-by-decorator';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import createSpy = jasmine.createSpy; import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
xdescribe('BrowseBySwitcherComponent', () => { describe('BrowseBySwitcherComponent', () => {
let comp: BrowseBySwitcherComponent; let comp: BrowseBySwitcherComponent;
let fixture: ComponentFixture<BrowseBySwitcherComponent>; let fixture: ComponentFixture<BrowseBySwitcherComponent>;
@@ -23,7 +22,8 @@ xdescribe('BrowseBySwitcherComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [BrowseBySwitcherComponent], declarations: [BrowseBySwitcherComponent],
providers: [ providers: [
{ provide: ActivatedRoute, useValue: activatedRouteStub } { provide: ActivatedRoute, useValue: activatedRouteStub },
{ provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).compileComponents(); }).compileComponents();
@@ -32,7 +32,6 @@ xdescribe('BrowseBySwitcherComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(BrowseBySwitcherComponent); fixture = TestBed.createComponent(BrowseBySwitcherComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
spyOnProperty(decorator, 'getComponentByBrowseByType').and.returnValue(createSpy('getComponentByItemType'));
})); }));
types.forEach((type) => { types.forEach((type) => {
@@ -43,7 +42,7 @@ xdescribe('BrowseBySwitcherComponent', () => {
}); });
it(`should call getComponentByBrowseByType with type "${type.type}"`, () => { it(`should call getComponentByBrowseByType with type "${type.type}"`, () => {
expect(decorator.getComponentByBrowseByType).toHaveBeenCalledWith(type.type); expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.type);
}); });
}); });
}); });

View File

@@ -1,10 +1,11 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface'; import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { getComponentByBrowseByType } from './browse-by-decorator'; import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { GenericConstructor } from '../../core/shared/generic-constructor';
@Component({ @Component({
selector: 'ds-browse-by-switcher', selector: 'ds-browse-by-switcher',
@@ -20,7 +21,8 @@ export class BrowseBySwitcherComponent implements OnInit {
*/ */
browseByComponent: Observable<any>; browseByComponent: Observable<any>;
public constructor(protected route: ActivatedRoute) { public constructor(protected route: ActivatedRoute,
@Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType) => GenericConstructor<any>) {
} }
/** /**
@@ -32,7 +34,7 @@ export class BrowseBySwitcherComponent implements OnInit {
const id = params.id; const id = params.id;
return environment.browseBy.types.find((config: BrowseByTypeConfig) => config.id === id); return environment.browseBy.types.find((config: BrowseByTypeConfig) => config.id === id);
}), }),
map((config: BrowseByTypeConfig) => getComponentByBrowseByType(config.type)) map((config: BrowseByTypeConfig) => this.getComponentByBrowseByType(config.type))
); );
} }

View File

@@ -9,6 +9,12 @@ import {
getFirstSucceededRemoteData getFirstSucceededRemoteData
} from '../../../../core/shared/operators'; } from '../../../../core/shared/operators';
import { hasValue } from '../../../../shared/empty.util'; import { hasValue } from '../../../../shared/empty.util';
import { InjectionToken } from '@angular/core';
export const PAGINATED_RELATIONS_TO_ITEMS_OPERATOR = new InjectionToken<(thisId: string) => (source: Observable<RemoteData<PaginatedList<Relationship>>>) => Observable<RemoteData<PaginatedList<Item>>>>('paginatedRelationsToItems', {
providedIn: 'root',
factory: () => paginatedRelationsToItems
});
/** /**
* Operator for comparing arrays using a mapping function * Operator for comparing arrays using a mapping function

View File

@@ -8,6 +8,20 @@ import {
TypedObject, TypedObject,
getResourceTypeValueFor getResourceTypeValueFor
} from '../object-cache.reducer'; } from '../object-cache.reducer';
import { InjectionToken } from '@angular/core';
export const DATA_SERVICE_FACTORY = new InjectionToken<(resourceType: ResourceType) => GenericConstructor<any>>('getDataServiceFor', {
providedIn: 'root',
factory: () => getDataServiceFor
});
export const LINK_DEFINITION_FACTORY = new InjectionToken<<T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']) => LinkDefinition<T>>('getLinkDefinition', {
providedIn: 'root',
factory: () => getLinkDefinition
});
export const LINK_DEFINITION_MAP_FACTORY = new InjectionToken<<T extends HALResource>(source: GenericConstructor<T>) => Map<keyof T['_links'], LinkDefinition<T>>>('getLinkDefinitions', {
providedIn: 'root',
factory: () => getLinkDefinitions
});
const resolvedLinkKey = Symbol('resolvedLink'); const resolvedLinkKey = Symbol('resolvedLink');

View File

@@ -5,15 +5,9 @@ import { FindListOptions } from '../../data/request.models';
import { HALLink } from '../../shared/hal-link.model'; import { HALLink } from '../../shared/hal-link.model';
import { HALResource } from '../../shared/hal-resource.model'; import { HALResource } from '../../shared/hal-resource.model';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
import * as decorators from './build-decorators';
import { LinkService } from './link.service'; import { LinkService } from './link.service';
import { DATA_SERVICE_FACTORY, LINK_DEFINITION_FACTORY, LINK_DEFINITION_MAP_FACTORY } from './build-decorators';
const spyOnFunction = <T>(obj: T, func: keyof T) => { import { isEmpty } from 'rxjs/operators';
const spy = jasmine.createSpy(func as string);
spyOnProperty(obj, func, 'get').and.returnValue(spy);
return spy;
};
const TEST_MODEL = new ResourceType('testmodel'); const TEST_MODEL = new ResourceType('testmodel');
let result: any; let result: any;
@@ -51,7 +45,7 @@ let testDataService: TestDataService;
let testModel: TestModel; let testModel: TestModel;
xdescribe('LinkService', () => { describe('LinkService', () => {
let service: LinkService; let service: LinkService;
beforeEach(() => { beforeEach(() => {
@@ -76,6 +70,30 @@ xdescribe('LinkService', () => {
providers: [LinkService, { providers: [LinkService, {
provide: TestDataService, provide: TestDataService,
useValue: testDataService useValue: testDataService
}, {
provide: DATA_SERVICE_FACTORY,
useValue: jasmine.createSpy('getDataServiceFor').and.returnValue(TestDataService),
}, {
provide: LINK_DEFINITION_FACTORY,
useValue: jasmine.createSpy('getLinkDefinition').and.returnValue({
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor'
}),
}, {
provide: LINK_DEFINITION_MAP_FACTORY,
useValue: jasmine.createSpy('getLinkDefinitions').and.returnValue([
{
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor',
},
{
resourceType: TEST_MODEL,
linkName: 'successor',
propertyName: 'successor',
}
]),
}] }]
}); });
service = TestBed.inject(LinkService); service = TestBed.inject(LinkService);
@@ -84,12 +102,6 @@ xdescribe('LinkService', () => {
describe('resolveLink', () => { describe('resolveLink', () => {
describe(`when the linkdefinition concerns a single object`, () => { describe(`when the linkdefinition concerns a single object`, () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor'
});
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor'))); service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
}); });
it('should call dataservice.findByHref with the correct href and nested links', () => { it('should call dataservice.findByHref with the correct href and nested links', () => {
@@ -98,13 +110,12 @@ xdescribe('LinkService', () => {
}); });
describe(`when the linkdefinition concerns a list`, () => { describe(`when the linkdefinition concerns a list`, () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({ ((service as any).getLinkDefinition as jasmine.Spy).and.returnValue({
resourceType: TEST_MODEL, resourceType: TEST_MODEL,
linkName: 'predecessor', linkName: 'predecessor',
propertyName: 'predecessor', propertyName: 'predecessor',
isList: true isList: true
}); });
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, true, true, followLink('successor'))); service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, true, true, followLink('successor')));
}); });
it('should call dataservice.findAllByHref with the correct href, findListOptions, and nested links', () => { it('should call dataservice.findAllByHref with the correct href, findListOptions, and nested links', () => {
@@ -113,21 +124,15 @@ xdescribe('LinkService', () => {
}); });
describe('either way', () => { describe('either way', () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor'
});
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
result = service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor'))); result = service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
}); });
it('should call getLinkDefinition with the correct model and link', () => { it('should call getLinkDefinition with the correct model and link', () => {
expect(decorators.getLinkDefinition).toHaveBeenCalledWith(testModel.constructor as any, 'predecessor'); expect((service as any).getLinkDefinition).toHaveBeenCalledWith(testModel.constructor as any, 'predecessor');
}); });
it('should call getDataServiceFor with the correct resource type', () => { it('should call getDataServiceFor with the correct resource type', () => {
expect(decorators.getDataServiceFor).toHaveBeenCalledWith(TEST_MODEL); expect((service as any).getDataServiceFor).toHaveBeenCalledWith(TEST_MODEL);
}); });
it('should return the model with the resolved link', () => { it('should return the model with the resolved link', () => {
@@ -140,7 +145,7 @@ xdescribe('LinkService', () => {
describe(`when the specified link doesn't exist on the model's class`, () => { describe(`when the specified link doesn't exist on the model's class`, () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue(undefined); ((service as any).getLinkDefinition as jasmine.Spy).and.returnValue(undefined);
}); });
it('should throw an error', () => { it('should throw an error', () => {
expect(() => { expect(() => {
@@ -151,12 +156,7 @@ xdescribe('LinkService', () => {
describe(`when there is no dataservice for the resourcetype in the link`, () => { describe(`when there is no dataservice for the resourcetype in the link`, () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({ ((service as any).getDataServiceFor as jasmine.Spy).and.returnValue(undefined);
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor'
});
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(undefined);
}); });
it('should throw an error', () => { it('should throw an error', () => {
expect(() => { expect(() => {
@@ -188,18 +188,6 @@ xdescribe('LinkService', () => {
beforeEach(() => { beforeEach(() => {
testModel.predecessor = 'predecessor value' as any; testModel.predecessor = 'predecessor value' as any;
testModel.successor = 'successor value' as any; testModel.successor = 'successor value' as any;
spyOnFunction(decorators, 'getLinkDefinitions').and.returnValue([
{
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor',
},
{
resourceType: TEST_MODEL,
linkName: 'successor',
propertyName: 'successor',
}
]);
}); });
it('should return a new version of the object without any resolved links', () => { it('should return a new version of the object without any resolved links', () => {
@@ -231,16 +219,10 @@ xdescribe('LinkService', () => {
} }
} }
}); });
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
}); });
describe('resolving the available link', () => { describe('resolving the available link', () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
resourceType: TEST_MODEL,
linkName: 'predecessor',
propertyName: 'predecessor'
});
result = service.resolveLinks(testModel, followLink('predecessor')); result = service.resolveLinks(testModel, followLink('predecessor'));
}); });
@@ -251,7 +233,7 @@ xdescribe('LinkService', () => {
describe('resolving the missing link', () => { describe('resolving the missing link', () => {
beforeEach(() => { beforeEach(() => {
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({ ((service as any).getLinkDefinition as jasmine.Spy).and.returnValue({
resourceType: TEST_MODEL, resourceType: TEST_MODEL,
linkName: 'successor', linkName: 'successor',
propertyName: 'successor' propertyName: 'successor'
@@ -259,8 +241,11 @@ xdescribe('LinkService', () => {
result = service.resolveLinks(testModel, followLink('successor')); result = service.resolveLinks(testModel, followLink('successor'));
}); });
it('should return the model with no resolved link', () => { it('should resolve to an empty observable', (done) => {
expect(result.successor).toBeUndefined(); result.successor.pipe(isEmpty()).subscribe((v) => {
expect(v).toEqual(true);
done();
});
}); });
}); });
}); });

View File

@@ -1,17 +1,18 @@
import { Injectable, Injector } from '@angular/core'; import { Inject, Injectable, Injector } from '@angular/core';
import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util';
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { GenericConstructor } from '../../shared/generic-constructor'; import { GenericConstructor } from '../../shared/generic-constructor';
import { HALResource } from '../../shared/hal-resource.model'; import { HALResource } from '../../shared/hal-resource.model';
import { import {
getDataServiceFor, DATA_SERVICE_FACTORY,
getLinkDefinition, LINK_DEFINITION_FACTORY,
getLinkDefinitions, LINK_DEFINITION_MAP_FACTORY,
LinkDefinition LinkDefinition
} from './build-decorators'; } from './build-decorators';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { EMPTY } from 'rxjs'; import { EMPTY } from 'rxjs';
import { ResourceType } from '../../shared/resource-type';
/** /**
* A Service to handle the resolving and removing * A Service to handle the resolving and removing
@@ -24,6 +25,9 @@ export class LinkService {
constructor( constructor(
protected parentInjector: Injector, protected parentInjector: Injector,
@Inject(DATA_SERVICE_FACTORY) private getDataServiceFor: (resourceType: ResourceType) => GenericConstructor<any>,
@Inject(LINK_DEFINITION_FACTORY) private getLinkDefinition: <T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']) => LinkDefinition<T>,
@Inject(LINK_DEFINITION_MAP_FACTORY) private getLinkDefinitions: <T extends HALResource>(source: GenericConstructor<T>) => Map<keyof T['_links'], LinkDefinition<T>>,
) { ) {
} }
@@ -49,12 +53,12 @@ export class LinkService {
* @param linkToFollow the {@link FollowLinkConfig} to resolve * @param linkToFollow the {@link FollowLinkConfig} to resolve
*/ */
public resolveLinkWithoutAttaching<T extends HALResource, U extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): Observable<RemoteData<U>> { public resolveLinkWithoutAttaching<T extends HALResource, U extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): Observable<RemoteData<U>> {
const matchingLinkDef = getLinkDefinition(model.constructor, linkToFollow.name); const matchingLinkDef = this.getLinkDefinition(model.constructor, linkToFollow.name);
if (hasNoValue(matchingLinkDef)) { if (hasNoValue(matchingLinkDef)) {
throw new Error(`followLink('${linkToFollow.name}') was used for a ${model.constructor.name}, but there is no property on ${model.constructor.name} models with an @link() for ${linkToFollow.name}`); throw new Error(`followLink('${linkToFollow.name}') was used for a ${model.constructor.name}, but there is no property on ${model.constructor.name} models with an @link() for ${linkToFollow.name}`);
} else { } else {
const provider = getDataServiceFor(matchingLinkDef.resourceType); const provider = this.getDataServiceFor(matchingLinkDef.resourceType);
if (hasNoValue(provider)) { if (hasNoValue(provider)) {
throw new Error(`The @link() for ${linkToFollow.name} on ${model.constructor.name} models uses the resource type ${matchingLinkDef.resourceType.value.toUpperCase()}, but there is no service with an @dataService(${matchingLinkDef.resourceType.value.toUpperCase()}) annotation in order to retrieve it`); throw new Error(`The @link() for ${linkToFollow.name} on ${model.constructor.name} models uses the resource type ${matchingLinkDef.resourceType.value.toUpperCase()}, but there is no service with an @dataService(${matchingLinkDef.resourceType.value.toUpperCase()}) annotation in order to retrieve it`);
@@ -104,7 +108,7 @@ export class LinkService {
*/ */
public removeResolvedLinks<T extends HALResource>(model: T): T { public removeResolvedLinks<T extends HALResource>(model: T): T {
const result = Object.assign(new (model.constructor as GenericConstructor<T>)(), model); const result = Object.assign(new (model.constructor as GenericConstructor<T>)(), model);
const linkDefs = getLinkDefinitions(model.constructor as GenericConstructor<T>); const linkDefs = this.getLinkDefinitions(model.constructor as GenericConstructor<T>);
if (isNotEmpty(linkDefs)) { if (isNotEmpty(linkDefs)) {
linkDefs.forEach((linkDef: LinkDefinition<T>) => { linkDefs.forEach((linkDef: LinkDefinition<T>) => {
result[linkDef.propertyName] = undefined; result[linkDef.propertyName] = undefined;

View File

@@ -1,5 +1,4 @@
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import * as ItemRelationshipsUtils from '../../+item-page/simple/item-types/shared/item-relationships-utils';
import { followLink } from '../../shared/utils/follow-link-config.model'; import { followLink } from '../../shared/utils/follow-link-config.model';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
@@ -15,9 +14,9 @@ import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-servic
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock'; import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock';
import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { getMockRequestService } from '../../shared/mocks/request.service.mock';
import { createPaginatedList, spyOnOperator } from '../../shared/testing/utils.test'; import { createPaginatedList } from '../../shared/testing/utils.test';
xdescribe('RelationshipService', () => { describe('RelationshipService', () => {
let service: RelationshipService; let service: RelationshipService;
let requestService: RequestService; let requestService: RequestService;
@@ -132,7 +131,8 @@ xdescribe('RelationshipService', () => {
null, null,
null, null,
null, null,
null null,
jasmine.createSpy('paginatedRelationsToItems').and.returnValue((v) => v),
); );
} }
@@ -195,8 +195,6 @@ xdescribe('RelationshipService', () => {
const rd$ = createSuccessfulRemoteDataObject$(relationsList); const rd$ = createSuccessfulRemoteDataObject$(relationsList);
spyOn(service, 'getItemRelationshipsByLabel').and.returnValue(rd$); spyOn(service, 'getItemRelationshipsByLabel').and.returnValue(rd$);
spyOnOperator(ItemRelationshipsUtils, 'paginatedRelationsToItems').and.returnValue((v) => v);
}); });
it('should call getItemRelationshipsByLabel with the correct params', (done) => { it('should call getItemRelationshipsByLabel with the correct params', (done) => {
@@ -225,7 +223,7 @@ xdescribe('RelationshipService', () => {
mockLabel, mockLabel,
mockOptions mockOptions
).subscribe((result) => { ).subscribe((result) => {
expect(ItemRelationshipsUtils.paginatedRelationsToItems).toHaveBeenCalledWith(mockItem.uuid); expect((service as any).paginatedRelationsToItems).toHaveBeenCalledWith(mockItem.uuid);
done(); done();
}); });
}); });

View File

@@ -1,11 +1,10 @@
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { MemoizedSelector, select, Store } from '@ngrx/store'; import { MemoizedSelector, select, Store } from '@ngrx/store';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
import { import {
compareArraysUsingIds, compareArraysUsingIds, PAGINATED_RELATIONS_TO_ITEMS_OPERATOR,
paginatedRelationsToItems,
relationsToItems relationsToItems
} from '../../+item-page/simple/item-types/shared/item-relationships-utils'; } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
import { AppState, keySelector } from '../../app.reducer'; import { AppState, keySelector } from '../../app.reducer';
@@ -87,7 +86,8 @@ export class RelationshipService extends DataService<Relationship> {
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected http: HttpClient, protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<Relationship>, protected comparator: DefaultChangeAnalyzer<Relationship>,
protected appStore: Store<AppState>) { protected appStore: Store<AppState>,
@Inject(PAGINATED_RELATIONS_TO_ITEMS_OPERATOR) private paginatedRelationsToItems: (thisId: string) => (source: Observable<RemoteData<PaginatedList<Relationship>>>) => Observable<RemoteData<PaginatedList<Item>>>) {
super(); super();
} }
@@ -254,7 +254,7 @@ export class RelationshipService extends DataService<Relationship> {
* @param options * @param options
*/ */
getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> { getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
return this.getItemRelationshipsByLabel(item, label, options, true, true, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType')).pipe(paginatedRelationsToItems(item.uuid)); return this.getItemRelationshipsByLabel(item, label, options, true, true, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType')).pipe(this.paginatedRelationsToItems(item.uuid));
} }
/** /**

View File

@@ -1,6 +1,6 @@
import { Router, UrlTree } from '@angular/router'; import { Router, UrlTree } from '@angular/router';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { filter, find, map, mergeMap, switchMap, take, takeWhile, tap } from 'rxjs/operators'; import { debounceTime, filter, find, map, mergeMap, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
import { SearchResult } from '../../shared/search/search-result.model'; import { SearchResult } from '../../shared/search/search-result.model';
import { PaginatedList } from '../data/paginated-list.model'; import { PaginatedList } from '../data/paginated-list.model';
@@ -15,6 +15,12 @@ import { DSpaceObject } from './dspace-object.model';
import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths'; import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths';
import { getEndUserAgreementPath } from '../../info/info-routing-paths'; import { getEndUserAgreementPath } from '../../info/info-routing-paths';
import { AuthService } from '../auth/auth.service'; import { AuthService } from '../auth/auth.service';
import { InjectionToken } from '@angular/core';
export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<<T>(dueTime: number) => (source: Observable<T>) => Observable<T>>('debounceTime', {
providedIn: 'root',
factory: () => debounceTime
});
/** /**
* This file contains custom RxJS operators that can be used in multiple places * This file contains custom RxJS operators that can be used in multiple places

View File

@@ -1,5 +1,4 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { BrowserKlaroService, COOKIE_MDFIELD } from './browser-klaro.service'; import { BrowserKlaroService, COOKIE_MDFIELD } from './browser-klaro.service';
import { getMockTranslateService } from '../mocks/translate.service.mock'; import { getMockTranslateService } from '../mocks/translate.service.mock';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
@@ -13,7 +12,7 @@ import { getTestScheduler } from 'jasmine-marbles';
import { MetadataValue } from '../../core/shared/metadata.models'; import { MetadataValue } from '../../core/shared/metadata.models';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
xdescribe('BrowserKlaroService', () => { describe('BrowserKlaroService', () => {
let translateService; let translateService;
let ePersonService; let ePersonService;
let authService; let authService;
@@ -81,7 +80,7 @@ xdescribe('BrowserKlaroService', () => {
} }
} }
}, },
apps: [{ services: [{
name: appName, name: appName,
purposes: [purpose] purposes: [purpose]
}], }],

View File

@@ -1,10 +1,7 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed, waitForAsync } from '@angular/core/testing';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { provideMockActions } from '@ngrx/effects/testing'; import { provideMockActions } from '@ngrx/effects/testing';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { RelationshipEffects } from './relationship.effects'; import { RelationshipEffects } from './relationship.effects';
import { AddRelationshipAction, RelationshipActionTypes, RemoveRelationshipAction } from './relationship.actions'; import { AddRelationshipAction, RelationshipActionTypes, RemoveRelationshipAction } from './relationship.actions';
import { Item } from '../../../../../core/shared/item.model'; import { Item } from '../../../../../core/shared/item.model';
@@ -23,6 +20,9 @@ import { RequestService } from '../../../../../core/data/request.service';
import { NotificationsService } from '../../../../notifications/notifications.service'; import { NotificationsService } from '../../../../notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
import { cold, hot } from 'jasmine-marbles';
import { DEBOUNCE_TIME_OPERATOR } from '../../../../../core/shared/operators';
import { last } from 'rxjs/operators';
describe('RelationshipEffects', () => { describe('RelationshipEffects', () => {
let relationEffects: RelationshipEffects; let relationEffects: RelationshipEffects;
@@ -51,7 +51,6 @@ describe('RelationshipEffects', () => {
let notificationsService; let notificationsService;
let translateService; let translateService;
let selectableListService; let selectableListService;
let testScheduler: TestScheduler;
function init() { function init() {
testUUID1 = '20e24c2f-a00a-467c-bdee-c929e79bf08d'; testUUID1 = '20e24c2f-a00a-467c-bdee-c929e79bf08d';
@@ -131,6 +130,7 @@ describe('RelationshipEffects', () => {
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
{ provide: TranslateService, useValue: translateService }, { provide: TranslateService, useValue: translateService },
{ provide: SelectableListService, useValue: selectableListService }, { provide: SelectableListService, useValue: selectableListService },
{ provide: DEBOUNCE_TIME_OPERATOR, useValue: jasmine.createSpy('debounceTime').and.returnValue((v) => v.pipe(last())) },
], ],
}); });
})); }));
@@ -140,9 +140,6 @@ describe('RelationshipEffects', () => {
identifier = (relationEffects as any).createIdentifier(leftItem, rightItem, relationshipType.leftwardType); identifier = (relationEffects as any).createIdentifier(leftItem, rightItem, relationshipType.leftwardType);
spyOn((relationEffects as any), 'addRelationship').and.stub(); spyOn((relationEffects as any), 'addRelationship').and.stub();
spyOn((relationEffects as any), 'removeRelationship').and.stub(); spyOn((relationEffects as any), 'removeRelationship').and.stub();
testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
}); });
describe('mapLastActions$', () => { describe('mapLastActions$', () => {
@@ -151,15 +148,13 @@ describe('RelationshipEffects', () => {
let action; let action;
it('should set the current value debounceMap and the value of the initialActionMap to ADD_RELATIONSHIP', () => { it('should set the current value debounceMap and the value of the initialActionMap to ADD_RELATIONSHIP', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { action = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
action = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--a-|', { a: action });
actions = hot('--a-|', { a: action }); const expected = cold('--b-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('--b-|', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush();
// TODO check following expectations with the implementation expect((relationEffects as any).initialActionMap[identifier]).toBe(action.type);
// expect((relationEffects as any).initialActionMap[identifier]).toBe(action.type); expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
// expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
});
}); });
}); });
@@ -172,14 +167,14 @@ describe('RelationshipEffects', () => {
}); });
it('should set the current value debounceMap to ADD_RELATIONSHIP but not change the value of the initialActionMap', () => { it('should set the current value debounceMap to ADD_RELATIONSHIP but not change the value of the initialActionMap', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { action = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
action = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--a-|', { a: action });
actions = hot('--a-|', { a: action });
expectObservable(relationEffects.mapLastActions$).toBe('--b-|', { b: undefined }); const expected = cold('--b-|', { b: undefined });
flush(); expect(relationEffects.mapLastActions$).toBeObservable(expected);
expect((relationEffects as any).initialActionMap[identifier]).toBe(testActionType);
expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type); expect((relationEffects as any).initialActionMap[identifier]).toBe(testActionType);
}); expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
}); });
}); });
@@ -188,30 +183,26 @@ describe('RelationshipEffects', () => {
describe('When the last value in the debounceMap is also an ADD_RELATIONSHIP action', () => { describe('When the last value in the debounceMap is also an ADD_RELATIONSHIP action', () => {
beforeEach(() => { beforeEach(() => {
(relationEffects as any).initialActionMap[identifier] = RelationshipActionTypes.ADD_RELATIONSHIP; (relationEffects as any).initialActionMap[identifier] = RelationshipActionTypes.ADD_RELATIONSHIP;
((relationEffects as any).debounceTime as jasmine.Spy).and.returnValue((v) => v);
}); });
it('should call addRelationship on the effect', () => { it('should call addRelationship on the effect', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { action = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
action = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--a-|', { a: action });
actions = hot('--a-|', { a: action }); const expected = cold('--b-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('--b-|', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush(); expect((relationEffects as any).addRelationship).toHaveBeenCalledWith(leftItem, rightItem, relationshipType.leftwardType, '1234', undefined);
expect((relationEffects as any).addRelationship).toHaveBeenCalledWith(leftItem, rightItem, relationshipType.leftwardType, '1234', undefined);
});
}); });
}); });
describe('When the last value in the debounceMap is instead a REMOVE_RELATIONSHIP action', () => { describe('When the last value in the debounceMap is instead a REMOVE_RELATIONSHIP action', () => {
it('should <b>not</b> call removeRelationship or addRelationship on the effect', () => { it('should <b>not</b> call removeRelationship or addRelationship on the effect', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { const actiona = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
const actiona = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); const actionb = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
const actionb = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--ab-|', { a: actiona, b: actionb });
actions = hot('--ab-|', { a: actiona, b: actionb }); const expected = cold('--bb-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('--bb-|', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush(); expect((relationEffects as any).addRelationship).not.toHaveBeenCalled();
expect((relationEffects as any).addRelationship).not.toHaveBeenCalled(); expect((relationEffects as any).removeRelationship).not.toHaveBeenCalled();
expect((relationEffects as any).removeRelationship).not.toHaveBeenCalled();
});
}); });
}); });
}); });
@@ -222,15 +213,13 @@ describe('RelationshipEffects', () => {
let action; let action;
it('should set the current value debounceMap and the value of the initialActionMap to REMOVE_RELATIONSHIP', () => { it('should set the current value debounceMap and the value of the initialActionMap to REMOVE_RELATIONSHIP', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { action = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
action = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--a-|', { a: action });
actions = hot('--a-|', { a: action }); const expected = cold('--b-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('--b-|', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush();
// TODO check following expectations with the implementation expect((relationEffects as any).initialActionMap[identifier]).toBe(action.type);
// expect((relationEffects as any).initialActionMap[identifier]).toBe(action.type); expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
// expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
});
}); });
}); });
@@ -238,20 +227,19 @@ describe('RelationshipEffects', () => {
let action; let action;
const testActionType = 'TEST_TYPE'; const testActionType = 'TEST_TYPE';
beforeEach(() => { beforeEach(() => {
((relationEffects as any).debounceTime as jasmine.Spy).and.returnValue((v) => v);
(relationEffects as any).initialActionMap[identifier] = testActionType; (relationEffects as any).initialActionMap[identifier] = testActionType;
(relationEffects as any).debounceMap[identifier] = new BehaviorSubject<string>(testActionType); (relationEffects as any).debounceMap[identifier] = new BehaviorSubject<string>(testActionType);
}); });
it('should set the current value debounceMap to REMOVE_RELATIONSHIP but not change the value of the initialActionMap', () => { it('should set the current value debounceMap to REMOVE_RELATIONSHIP but not change the value of the initialActionMap', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { action = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
action = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--a-|', { a: action });
actions = hot('--a-|', { a: action }); const expected = cold('--b-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('--b-|', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush();
// TODO check following expectations with the implementation expect((relationEffects as any).initialActionMap[identifier]).toBe(testActionType);
// expect((relationEffects as any).initialActionMap[identifier]).toBe(testActionType); expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
// expect((relationEffects as any).debounceMap[identifier].value).toBe(action.type);
});
}); });
}); });
@@ -259,32 +247,28 @@ describe('RelationshipEffects', () => {
let action; let action;
describe('When the last value in the debounceMap is also an REMOVE_RELATIONSHIP action', () => { describe('When the last value in the debounceMap is also an REMOVE_RELATIONSHIP action', () => {
beforeEach(() => { beforeEach(() => {
((relationEffects as any).debounceTime as jasmine.Spy).and.returnValue((v) => v);
(relationEffects as any).initialActionMap[identifier] = RelationshipActionTypes.REMOVE_RELATIONSHIP; (relationEffects as any).initialActionMap[identifier] = RelationshipActionTypes.REMOVE_RELATIONSHIP;
}); });
it('should call removeRelationship on the effect', () => { it('should call removeRelationship on the effect', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { action = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
action = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--a-|', { a: action });
actions = hot('a', { a: action }); const expected = cold('--b-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('b', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush(); expect((relationEffects as any).removeRelationship).toHaveBeenCalledWith(leftItem, rightItem, relationshipType.leftwardType, '1234',);
expect((relationEffects as any).removeRelationship).toHaveBeenCalledWith(leftItem, rightItem, relationshipType.leftwardType, '1234',);
});
}); });
}); });
describe('When the last value in the debounceMap is instead a ADD_RELATIONSHIP action', () => { describe('When the last value in the debounceMap is instead a ADD_RELATIONSHIP action', () => {
it('should <b>not</b> call addRelationship or removeRelationship on the effect', () => { it('should <b>not</b> call addRelationship or removeRelationship on the effect', () => {
testScheduler.run(({ hot, expectObservable, flush }) => { const actionb = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
const actionb = new RemoveRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); const actiona = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234');
const actiona = new AddRelationshipAction(leftItem, rightItem, relationshipType.leftwardType, '1234'); actions = hot('--ab-|', { a: actiona, b: actionb });
actions = hot('--ab-|', { a: actiona, b: actionb }); const expected = cold('--bb-|', { b: undefined });
expectObservable(relationEffects.mapLastActions$).toBe('--bb-|', { b: undefined }); expect(relationEffects.mapLastActions$).toBeObservable(expected);
flush(); expect((relationEffects as any).addRelationship).not.toHaveBeenCalled();
expect((relationEffects as any).addRelationship).not.toHaveBeenCalled(); expect((relationEffects as any).removeRelationship).not.toHaveBeenCalled();
expect((relationEffects as any).removeRelationship).not.toHaveBeenCalled();
});
}); });
}); });
}); });

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { debounceTime, filter, map, mergeMap, switchMap, take } from 'rxjs/operators'; import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { RelationshipService } from '../../../../../core/data/relationship.service'; import { RelationshipService } from '../../../../../core/data/relationship.service';
import { import {
getRemoteDataPayload, getRemoteDataPayload,
getFirstSucceededRemoteData getFirstSucceededRemoteData, DEBOUNCE_TIME_OPERATOR
} from '../../../../../core/shared/operators'; } from '../../../../../core/shared/operators';
import { import {
AddRelationshipAction, AddRelationshipAction,
@@ -71,7 +71,7 @@ export class RelationshipEffects {
this.initialActionMap[identifier] = action.type; this.initialActionMap[identifier] = action.type;
this.debounceMap[identifier] = new BehaviorSubject<string>(action.type); this.debounceMap[identifier] = new BehaviorSubject<string>(action.type);
this.debounceMap[identifier].pipe( this.debounceMap[identifier].pipe(
debounceTime(DEBOUNCE_TIME), this.debounceTime(DEBOUNCE_TIME),
take(1) take(1)
).subscribe( ).subscribe(
(type) => { (type) => {
@@ -159,6 +159,7 @@ export class RelationshipEffects {
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private translateService: TranslateService, private translateService: TranslateService,
private selectableListService: SelectableListService, private selectableListService: SelectableListService,
@Inject(DEBOUNCE_TIME_OPERATOR) private debounceTime: <T>(dueTime: number) => (source: Observable<T>) => Observable<T>,
) { ) {
} }

View File

@@ -1,15 +1,15 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ChangeDetectionStrategy, ComponentFactoryResolver, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { Context } from '../../core/shared/context.model'; import { Context } from '../../core/shared/context.model';
import { import {
MetadataRepresentation, MetadataRepresentation,
MetadataRepresentationType MetadataRepresentationType
} from '../../core/shared/metadata-representation/metadata-representation.model'; } from '../../core/shared/metadata-representation/metadata-representation.model';
import { MetadataRepresentationLoaderComponent } from './metadata-representation-loader.component'; import { MetadataRepresentationLoaderComponent } from './metadata-representation-loader.component';
import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
import { spyOnExported } from '../testing/utils.test';
import { MetadataRepresentationDirective } from './metadata-representation.directive'; import { MetadataRepresentationDirective } from './metadata-representation.directive';
import * as metadataRepresentationDecorator from './metadata-representation.decorator'; import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator';
import { ThemeService } from '../theme-support/theme.service';
import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
const testType = 'TestType'; const testType = 'TestType';
const testContext = Context.Search; const testContext = Context.Search;
@@ -29,16 +29,30 @@ class TestType implements MetadataRepresentation {
} }
} }
xdescribe('MetadataRepresentationLoaderComponent', () => { describe('MetadataRepresentationLoaderComponent', () => {
let comp: MetadataRepresentationLoaderComponent; let comp: MetadataRepresentationLoaderComponent;
let fixture: ComponentFixture<MetadataRepresentationLoaderComponent>; let fixture: ComponentFixture<MetadataRepresentationLoaderComponent>;
let themeService: ThemeService;
const themeName = 'test-theme';
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
themeService = jasmine.createSpyObj('themeService', {
getThemeName: themeName,
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [], imports: [],
declarations: [MetadataRepresentationLoaderComponent, PlainTextMetadataListElementComponent, MetadataRepresentationDirective], declarations: [MetadataRepresentationLoaderComponent, PlainTextMetadataListElementComponent, MetadataRepresentationDirective],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
providers: [ComponentFactoryResolver] providers: [
{
provide: METADATA_REPRESENTATION_COMPONENT_FACTORY,
useValue: jasmine.createSpy('getMetadataRepresentationComponent').and.returnValue(PlainTextMetadataListElementComponent)
},
{
provide: ThemeService,
useValue: themeService,
}
]
}).overrideComponent(MetadataRepresentationLoaderComponent, { }).overrideComponent(MetadataRepresentationLoaderComponent, {
set: { set: {
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.Default,
@@ -53,15 +67,12 @@ xdescribe('MetadataRepresentationLoaderComponent', () => {
comp.mdRepresentation = new TestType(); comp.mdRepresentation = new TestType();
comp.context = testContext; comp.context = testContext;
spyOnExported(metadataRepresentationDecorator, 'getMetadataRepresentationComponent').and.returnValue(PlainTextMetadataListElementComponent);
fixture.detectChanges(); fixture.detectChanges();
})); }));
describe('When the component is rendered', () => { describe('When the component is rendered', () => {
it('should call the getMetadataRepresentationComponent function with the right entity type, representation type and context', () => { it('should call the getMetadataRepresentationComponent function with the right entity type, representation type and context', () => {
expect(metadataRepresentationDecorator.getMetadataRepresentationComponent).toHaveBeenCalledWith(testType, testRepresentationType, testContext); expect((comp as any).getMetadataRepresentationComponent).toHaveBeenCalledWith(testType, testRepresentationType, testContext, themeName);
}); });
}); });
}); });

View File

@@ -1,6 +1,9 @@
import { Component, ComponentFactoryResolver, Input, OnInit, ViewChild } from '@angular/core'; import { Component, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild } from '@angular/core';
import { MetadataRepresentation } from '../../core/shared/metadata-representation/metadata-representation.model'; import {
import { getMetadataRepresentationComponent } from './metadata-representation.decorator'; MetadataRepresentation,
MetadataRepresentationType
} from '../../core/shared/metadata-representation/metadata-representation.model';
import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator';
import { Context } from '../../core/shared/context.model'; import { Context } from '../../core/shared/context.model';
import { GenericConstructor } from '../../core/shared/generic-constructor'; import { GenericConstructor } from '../../core/shared/generic-constructor';
import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component'; import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component';
@@ -45,7 +48,8 @@ export class MetadataRepresentationLoaderComponent implements OnInit {
constructor( constructor(
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
private themeService: ThemeService private themeService: ThemeService,
@Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor<any>,
) { ) {
} }
@@ -68,6 +72,6 @@ export class MetadataRepresentationLoaderComponent implements OnInit {
* @returns {string} * @returns {string}
*/ */
private getComponent(): GenericConstructor<MetadataRepresentationListElementComponent> { private getComponent(): GenericConstructor<MetadataRepresentationListElementComponent> {
return getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName()); return this.getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName());
} }
} }

View File

@@ -1,6 +1,13 @@
import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model'; import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model';
import { hasNoValue, hasValue } from '../empty.util'; import { hasNoValue, hasValue } from '../empty.util';
import { Context } from '../../core/shared/context.model'; import { Context } from '../../core/shared/context.model';
import { InjectionToken } from '@angular/core';
import { GenericConstructor } from '../../core/shared/generic-constructor';
export const METADATA_REPRESENTATION_COMPONENT_FACTORY = new InjectionToken<(entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor<any>>('getMetadataRepresentationComponent', {
providedIn: 'root',
factory: () => getMetadataRepresentationComponent
});
export const map = new Map(); export const map = new Map();