added tests and docs

This commit is contained in:
lotte
2020-02-26 16:53:10 +01:00
parent 4ae8997ada
commit b554d40e9c
15 changed files with 152 additions and 28 deletions

View File

@@ -83,7 +83,7 @@ describe('CollectionItemMapperComponent', () => {
const itemDataServiceStub = { const itemDataServiceStub = {
mapToCollection: () => of(new RestResponse(true, 200, 'OK')) mapToCollection: () => of(new RestResponse(true, 200, 'OK'))
}; };
const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollectionRD }); const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockCollectionRD });
const translateServiceStub = { const translateServiceStub = {
get: () => of('test-message of collection ' + mockCollection.name), get: () => of('test-message of collection ' + mockCollection.name),
onLangChange: new EventEmitter(), onLangChange: new EventEmitter(),

View File

@@ -5,14 +5,28 @@ import { hasNoValue, hasValue, isNotUndefined } from '../shared/empty.util';
import { filter, map, switchMap, tap } from 'rxjs/operators'; import { filter, map, switchMap, tap } from 'rxjs/operators';
import { combineLatest, Observable, Subscription, of as observableOf } from 'rxjs'; import { combineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
/**
* Component representing the breadcrumbs of a page
*/
@Component({ @Component({
selector: 'ds-breadcrumbs', selector: 'ds-breadcrumbs',
templateUrl: './breadcrumbs.component.html', templateUrl: './breadcrumbs.component.html',
styleUrls: ['./breadcrumbs.component.scss'] styleUrls: ['./breadcrumbs.component.scss']
}) })
export class BreadcrumbsComponent implements OnInit, OnDestroy { export class BreadcrumbsComponent implements OnInit, OnDestroy {
/**
* List of breadcrumbs for this page
*/
breadcrumbs: Breadcrumb[]; breadcrumbs: Breadcrumb[];
/**
* Whether or not to show breadcrumbs on this page
*/
showBreadcrumbs: boolean; showBreadcrumbs: boolean;
/**
* Subscription to unsubscribe from on destroy
*/
subscription: Subscription; subscription: Subscription;
constructor( constructor(
@@ -21,6 +35,9 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
) { ) {
} }
/**
* Sets the breadcrumbs on init for this page
*/
ngOnInit(): void { ngOnInit(): void {
this.subscription = this.router.events.pipe( this.subscription = this.router.events.pipe(
filter((e): e is NavigationEnd => e instanceof NavigationEnd), filter((e): e is NavigationEnd => e instanceof NavigationEnd),
@@ -32,6 +49,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
) )
} }
/**
* Method that recursively resolves breadcrumbs
* @param route The route to get the breadcrumb from
*/
resolveBreadcrumbs(route: ActivatedRoute): Observable<Breadcrumb[]> { resolveBreadcrumbs(route: ActivatedRoute): Observable<Breadcrumb[]> {
const data = route.snapshot.data; const data = route.snapshot.data;
const routeConfig = route.snapshot.routeConfig; const routeConfig = route.snapshot.routeConfig;
@@ -56,12 +77,18 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]); return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
} }
/**
* Unsubscribe from subscription
*/
ngOnDestroy(): void { ngOnDestroy(): void {
if (hasValue(this.subscription)) { if (hasValue(this.subscription)) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
} }
/**
* Resets the state of the breadcrumbs
*/
reset() { reset() {
this.breadcrumbs = []; this.breadcrumbs = [];
this.showBreadcrumbs = true; this.showBreadcrumbs = true;

View File

@@ -1,6 +1,15 @@
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model'; import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
/**
* Service to calculate breadcrumbs for a single part of the route
*/
export interface BreadcrumbsService<T> { export interface BreadcrumbsService<T> {
/**
* Method to calculate the breadcrumbs for a part of the route
* @param key The key used to resolve the breadcrumb
* @param url The url to use as a link for this breadcrumb
*/
getBreadcrumbs(key: T, url: string): Observable<Breadcrumb[]>; getBreadcrumbs(key: T, url: string): Observable<Breadcrumb[]>;
} }

View File

@@ -5,7 +5,7 @@ import { Collection } from '../shared/collection.model';
import { CollectionDataService } from '../data/collection-data.service'; import { CollectionDataService } from '../data/collection-data.service';
/** /**
* The class that resolve the BreadcrumbConfig object for a route * The class that resolves the BreadcrumbConfig object for a Collection
*/ */
@Injectable() @Injectable()
export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> { export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> {

View File

@@ -5,7 +5,7 @@ import { CommunityDataService } from '../data/community-data.service';
import { Community } from '../shared/community.model'; import { Community } from '../shared/community.model';
/** /**
* The class that resolve the BreadcrumbConfig object for a route * The class that resolves the BreadcrumbConfig object for a Community
*/ */
@Injectable() @Injectable()
export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> { export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> {

View File

@@ -0,0 +1,34 @@
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
import { Collection } from '../shared/collection.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
import { getTestScheduler } from 'jasmine-marbles';
describe('DSOBreadcrumbResolver', () => {
describe('resolve', () => {
let resolver: DSOBreadcrumbResolver<Collection>;
let collectionService: any;
let dsoBreadcrumbService: any;
let testCollection: Collection;
let uuid;
let breadcrumbUrl;
let currentUrl;
beforeEach(() => {
uuid = '1234-65487-12354-1235';
breadcrumbUrl = '/collections/' + uuid;
currentUrl = breadcrumbUrl + '/edit';
testCollection = Object.assign(new Collection(), { uuid });
dsoBreadcrumbService = {};
collectionService = {
findById: (id: string) => createSuccessfulRemoteDataObject$(testCollection)
};
resolver = new DSOBreadcrumbResolver(dsoBreadcrumbService, collectionService);
});
it('should resolve a breadcrumb config for the correct DSO', () => {
const resolvedConfig = resolver.resolve({ params: { id: uuid } } as any, { url: currentUrl } as any);
const expectedConfig = { provider: dsoBreadcrumbService, key: testCollection, url: breadcrumbUrl };
getTestScheduler().expectObservable(resolvedConfig).toBe('(a|)', { a: expectedConfig})
});
});
});

View File

@@ -10,7 +10,7 @@ import { DSpaceObject } from '../shared/dspace-object.model';
import { ChildHALResource } from '../shared/child-hal-resource.model'; import { ChildHALResource } from '../shared/child-hal-resource.model';
/** /**
* The class that resolve the BreadcrumbConfig object for a route * The class that resolves the BreadcrumbConfig object for a DSpaceObject
*/ */
@Injectable() @Injectable()
export class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> implements Resolve<BreadcrumbConfig<T>> { export class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> implements Resolve<BreadcrumbConfig<T>> {
@@ -18,7 +18,7 @@ export class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> im
} }
/** /**
* Method for resolving a site object * Method for resolving a breadcrumb config object
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot * @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns BreadcrumbConfig object * @returns BreadcrumbConfig object

View File

@@ -5,20 +5,15 @@ import { LinkService } from '../cache/builders/link.service';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
import { DSpaceObject } from '../shared/dspace-object.model'; import { DSpaceObject } from '../shared/dspace-object.model';
import { map } from 'rxjs/operators';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RemoteData } from '../data/remote-data';
import { hasValue } from '../../shared/empty.util';
import { Community } from '../shared/community.model'; import { Community } from '../shared/community.model';
import { Collection } from '../shared/collection.model'; import { Collection } from '../shared/collection.model';
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model'; import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { getItemPageRoute } from '../../+item-page/item-page-routing.module'; import { getTestScheduler } from 'jasmine-marbles';
import { getCommunityPageRoute } from '../../+community-page/community-page-routing.module';
import { getCollectionPageRoute } from '../../+collection-page/collection-page-routing.module';
import { cold, getTestScheduler } from 'jasmine-marbles';
import { getDSOPath } from '../../app-routing.module'; import { getDSOPath } from '../../app-routing.module';
import { DSONameService } from './dso-name.service';
fdescribe('DSOBreadcrumbsService', () => { describe('DSOBreadcrumbsService', () => {
let service: DSOBreadcrumbsService; let service: DSOBreadcrumbsService;
let linkService: any; let linkService: any;
let testItem; let testItem;
@@ -33,7 +28,7 @@ fdescribe('DSOBreadcrumbsService', () => {
let collectionUUID; let collectionUUID;
let communityUUID; let communityUUID;
let objects: DSpaceObject[]; let dsoNameService;
function init() { function init() {
itemPath = '/items/'; itemPath = '/items/';
@@ -47,7 +42,9 @@ fdescribe('DSOBreadcrumbsService', () => {
testCommunity = Object.assign(new Community(), testCommunity = Object.assign(new Community(),
{ {
type: 'community', type: 'community',
name: 'community', metadata: {
'dc.title': [{value: 'community'}]
},
uuid: communityUUID, uuid: communityUUID,
parentCommunity: observableOf(Object.assign(createSuccessfulRemoteDataObject(undefined), { statusCode: 204 })), parentCommunity: observableOf(Object.assign(createSuccessfulRemoteDataObject(undefined), { statusCode: 204 })),
@@ -61,7 +58,9 @@ fdescribe('DSOBreadcrumbsService', () => {
testCollection = Object.assign(new Collection(), testCollection = Object.assign(new Collection(),
{ {
type: 'collection', type: 'collection',
name: 'collection', metadata: {
'dc.title': [{value: 'collection'}]
},
uuid: collectionUUID, uuid: collectionUUID,
parentCommunity: createSuccessfulRemoteDataObject$(testCommunity), parentCommunity: createSuccessfulRemoteDataObject$(testCommunity),
_links: { _links: {
@@ -74,7 +73,9 @@ fdescribe('DSOBreadcrumbsService', () => {
testItem = Object.assign(new Item(), testItem = Object.assign(new Item(),
{ {
type: 'item', type: 'item',
name: 'item', metadata: {
'dc.title': [{value: 'item'}]
},
uuid: itemUUID, uuid: itemUUID,
owningCollection: createSuccessfulRemoteDataObject$(testCollection), owningCollection: createSuccessfulRemoteDataObject$(testCollection),
_links: { _links: {
@@ -84,15 +85,15 @@ fdescribe('DSOBreadcrumbsService', () => {
} }
); );
objects = [testItem, testCollection, testCommunity]; dsoNameService = { getName: (dso) => getName(dso) }
} }
beforeEach(async(() => { beforeEach(async(() => {
init(); init();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
{ provide: LinkService, useValue: getMockLinkService() } { provide: LinkService, useValue: getMockLinkService() },
{ provide: DSONameService, useValue: dsoNameService }
] ]
}).compileComponents(); }).compileComponents();
})); }));
@@ -100,18 +101,22 @@ fdescribe('DSOBreadcrumbsService', () => {
beforeEach(() => { beforeEach(() => {
linkService = TestBed.get(LinkService); linkService = TestBed.get(LinkService);
linkService.resolveLink.and.callFake((object, link) => object); linkService.resolveLink.and.callFake((object, link) => object);
service = new DSOBreadcrumbsService(linkService); service = new DSOBreadcrumbsService(linkService, dsoNameService);
}); });
describe('getBreadcrumbs', () => { describe('getBreadcrumbs', () => {
it('should return the breadcrumbs based on an Item', () => { it('should return the breadcrumbs based on an Item', () => {
const breadcrumbs = service.getBreadcrumbs(testItem, testItem._links.self); const breadcrumbs = service.getBreadcrumbs(testItem, testItem._links.self);
const expectedCrumbs = [ const expectedCrumbs = [
new Breadcrumb(testCommunity.name, getDSOPath(testCommunity)), new Breadcrumb(getName(testCommunity), getDSOPath(testCommunity)),
new Breadcrumb(testCollection.name, getDSOPath(testCollection)), new Breadcrumb(getName(testCollection), getDSOPath(testCollection)),
new Breadcrumb(testItem.name, getDSOPath(testItem)), new Breadcrumb(getName(testItem), getDSOPath(testItem)),
]; ];
getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedCrumbs }); getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedCrumbs });
}) })
}); });
function getName(dso: DSpaceObject): string {
return dso.metadata['dc.title'][0].value
}
}); });

View File

@@ -12,6 +12,9 @@ import { RemoteData } from '../data/remote-data';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
/**
* Service to calculate DSpaceObject breadcrumbs for a single part of the route
*/
@Injectable() @Injectable()
export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResource & DSpaceObject> { export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResource & DSpaceObject> {
constructor( constructor(
@@ -21,6 +24,12 @@ export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResourc
} }
/**
* Method to recursively calculate the breadcrumbs
* This method returns the name and url of the key and all its parent DSO's recursively, top down
* @param key The key (a DSpaceObject) used to resolve the breadcrumb
* @param url The url to use as a link for this breadcrumb
*/
getBreadcrumbs(key: ChildHALResource & DSpaceObject, url: string): Observable<Breadcrumb[]> { getBreadcrumbs(key: ChildHALResource & DSpaceObject, url: string): Observable<Breadcrumb[]> {
const label = this.dsoNameService.getName(key); const label = this.dsoNameService.getName(key);
const crumb = new Breadcrumb(label, url); const crumb = new Breadcrumb(label, url);

View File

@@ -0,0 +1,28 @@
import { I18nBreadcrumbResolver } from './i18n-breadcrumb.resolver';
describe('I18nBreadcrumbResolver', () => {
describe('resolve', () => {
let resolver: I18nBreadcrumbResolver;
let i18nBreadcrumbService: any;
let i18nKey: string;
let path: string;
beforeEach(() => {
i18nKey = 'example.key';
path = 'rest.com/path/to/breadcrumb';
i18nBreadcrumbService = {};
resolver = new I18nBreadcrumbResolver(i18nBreadcrumbService);
});
it('should resolve the breadcrumb config', () => {
const resolvedConfig = resolver.resolve({ data: { breadcrumbKey: i18nKey }, url: [path] } as any, {} as any);
const expectedConfig = { provider: i18nBreadcrumbService, key: i18nKey, url: path };
expect(resolvedConfig).toEqual(expectedConfig);
});
it('should resolve throw an error when no breadcrumbKey is defined', () => {
expect(() => {
resolver.resolve({ data: {} } as any, undefined)
}).toThrow();
});
});
});

View File

@@ -5,7 +5,7 @@ import { I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
import { hasNoValue } from '../../shared/empty.util'; import { hasNoValue } from '../../shared/empty.util';
/** /**
* The class that resolve the BreadcrumbConfig object for a route * The class that resolves a BreadcrumbConfig object with an i18n key string for a route
*/ */
@Injectable() @Injectable()
export class I18nBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> { export class I18nBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> {

View File

@@ -3,7 +3,7 @@ import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { getTestScheduler } from 'jasmine-marbles'; import { getTestScheduler } from 'jasmine-marbles';
import { BREADCRUMB_MESSAGE_POSTFIX, I18nBreadcrumbsService } from './i18n-breadcrumbs.service'; import { BREADCRUMB_MESSAGE_POSTFIX, I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
fdescribe('I18nBreadcrumbsService', () => { describe('I18nBreadcrumbsService', () => {
let service: I18nBreadcrumbsService; let service: I18nBreadcrumbsService;
let exampleString; let exampleString;
let exampleURL; let exampleURL;

View File

@@ -3,10 +3,22 @@ import { BreadcrumbsService } from './breadcrumbs.service';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
/**
* The postfix for i18n breadcrumbs
*/
export const BREADCRUMB_MESSAGE_POSTFIX = '.breadcrumbs'; export const BREADCRUMB_MESSAGE_POSTFIX = '.breadcrumbs';
/**
* Service to calculate i18n breadcrumbs for a single part of the route
*/
@Injectable() @Injectable()
export class I18nBreadcrumbsService implements BreadcrumbsService<string> { export class I18nBreadcrumbsService implements BreadcrumbsService<string> {
/**
* Method to calculate the breadcrumbs
* @param key The key used to resolve the breadcrumb
* @param url The url to use as a link for this breadcrumb
*/
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> { getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
return observableOf([new Breadcrumb(key + BREADCRUMB_MESSAGE_POSTFIX, url)]); return observableOf([new Breadcrumb(key + BREADCRUMB_MESSAGE_POSTFIX, url)]);
} }

View File

@@ -5,7 +5,7 @@ import { Item } from '../shared/item.model';
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver'; import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
/** /**
* The class that resolve the BreadcrumbConfig object for a route * The class that resolves the BreadcrumbConfig object for an Item
*/ */
@Injectable() @Injectable()
export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> { export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {

View File

@@ -13,7 +13,7 @@ const mockItem = Object.assign(new Item(), { metadata: { 'dc.description': [{ va
const virtMD = Object.assign(new MetadataValue(), { value: organisation }); const virtMD = Object.assign(new MetadataValue(), { value: organisation });
const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(virtMD), mockItem); const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(virtMD), mockItem);
describe('OrgUnitItemMetadataListElementComponent', () => { fdescribe('OrgUnitItemMetadataListElementComponent', () => {
let comp: OrgUnitItemMetadataListElementComponent; let comp: OrgUnitItemMetadataListElementComponent;
let fixture: ComponentFixture<OrgUnitItemMetadataListElementComponent>; let fixture: ComponentFixture<OrgUnitItemMetadataListElementComponent>;