Added type doc and test

This commit is contained in:
lotte
2019-03-29 16:39:25 +01:00
parent 030709aef1
commit 950bfb6381
11 changed files with 410 additions and 65 deletions

View File

@@ -6,13 +6,24 @@ import { PaginatedList } from '../../../core/data/paginated-list';
import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model'; import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
/**
* This component renders a list of bitstream formats
*/
@Component({ @Component({
selector: 'ds-bitstream-formats', selector: 'ds-bitstream-formats',
templateUrl: './bitstream-formats.component.html' templateUrl: './bitstream-formats.component.html'
}) })
export class BitstreamFormatsComponent { export class BitstreamFormatsComponent {
/**
* A paginated list of bitstream formats to be shown on the page
*/
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>; bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
/**
* The current pagination configuration for the page
* Currently simply renders all bitstream formats
*/
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'registry-bitstreamformats-pagination', id: 'registry-bitstreamformats-pagination',
pageSize: 10000 pageSize: 10000
@@ -22,11 +33,18 @@ export class BitstreamFormatsComponent {
this.updateFormats(); this.updateFormats();
} }
/**
* When the page is changed, make sure to update the list of bitstreams to match the new page
* @param event The page change event
*/
onPageChange(event) { onPageChange(event) {
this.config.currentPage = event; this.config.currentPage = event;
this.updateFormats(); this.updateFormats();
} }
/**
* Method to update the bitstream formats that are shown
*/
private updateFormats() { private updateFormats() {
this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config); this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config);
} }

View File

@@ -28,7 +28,7 @@ export const MetadataRegistryActionTypes = {
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
/** /**
* Used to collapse the sidebar * Used to edit a metadata schema in the metadata registry
*/ */
export class MetadataRegistryEditSchemaAction implements Action { export class MetadataRegistryEditSchemaAction implements Action {
type = MetadataRegistryActionTypes.EDIT_SCHEMA; type = MetadataRegistryActionTypes.EDIT_SCHEMA;
@@ -41,12 +41,15 @@ export class MetadataRegistryEditSchemaAction implements Action {
} }
/** /**
* Used to expand the sidebar * Used to cancel the editing of a metadata schema in the metadata registry
*/ */
export class MetadataRegistryCancelSchemaAction implements Action { export class MetadataRegistryCancelSchemaAction implements Action {
type = MetadataRegistryActionTypes.CANCEL_EDIT_SCHEMA; type = MetadataRegistryActionTypes.CANCEL_EDIT_SCHEMA;
} }
/**
* Used to select a single metadata schema in the metadata registry
*/
export class MetadataRegistrySelectSchemaAction implements Action { export class MetadataRegistrySelectSchemaAction implements Action {
type = MetadataRegistryActionTypes.SELECT_SCHEMA; type = MetadataRegistryActionTypes.SELECT_SCHEMA;
@@ -57,6 +60,9 @@ export class MetadataRegistrySelectSchemaAction implements Action {
} }
} }
/**
* Used to deselect a single metadata schema in the metadata registry
*/
export class MetadataRegistryDeselectSchemaAction implements Action { export class MetadataRegistryDeselectSchemaAction implements Action {
type = MetadataRegistryActionTypes.DESELECT_SCHEMA; type = MetadataRegistryActionTypes.DESELECT_SCHEMA;
@@ -67,12 +73,15 @@ export class MetadataRegistryDeselectSchemaAction implements Action {
} }
} }
/**
* Used to deselect all metadata schemas in the metadata registry
*/
export class MetadataRegistryDeselectAllSchemaAction implements Action { export class MetadataRegistryDeselectAllSchemaAction implements Action {
type = MetadataRegistryActionTypes.DESELECT_ALL_SCHEMA; type = MetadataRegistryActionTypes.DESELECT_ALL_SCHEMA;
} }
/** /**
* Used to collapse the sidebar * Used to edit a metadata field in the metadata registry
*/ */
export class MetadataRegistryEditFieldAction implements Action { export class MetadataRegistryEditFieldAction implements Action {
type = MetadataRegistryActionTypes.EDIT_FIELD; type = MetadataRegistryActionTypes.EDIT_FIELD;
@@ -85,12 +94,15 @@ export class MetadataRegistryEditFieldAction implements Action {
} }
/** /**
* Used to expand the sidebar * Used to cancel the editing of a metadata field in the metadata registry
*/ */
export class MetadataRegistryCancelFieldAction implements Action { export class MetadataRegistryCancelFieldAction implements Action {
type = MetadataRegistryActionTypes.CANCEL_EDIT_FIELD; type = MetadataRegistryActionTypes.CANCEL_EDIT_FIELD;
} }
/**
* Used to select a single metadata field in the metadata registry
*/
export class MetadataRegistrySelectFieldAction implements Action { export class MetadataRegistrySelectFieldAction implements Action {
type = MetadataRegistryActionTypes.SELECT_FIELD; type = MetadataRegistryActionTypes.SELECT_FIELD;
@@ -101,6 +113,9 @@ export class MetadataRegistrySelectFieldAction implements Action {
} }
} }
/**
* Used to deselect a single metadata field in the metadata registry
*/
export class MetadataRegistryDeselectFieldAction implements Action { export class MetadataRegistryDeselectFieldAction implements Action {
type = MetadataRegistryActionTypes.DESELECT_FIELD; type = MetadataRegistryActionTypes.DESELECT_FIELD;
@@ -111,6 +126,9 @@ export class MetadataRegistryDeselectFieldAction implements Action {
} }
} }
/**
* Used to deselect all metadata fields in the metadata registry
*/
export class MetadataRegistryDeselectAllFieldAction implements Action { export class MetadataRegistryDeselectAllFieldAction implements Action {
type = MetadataRegistryActionTypes.DESELECT_ALL_FIELD; type = MetadataRegistryActionTypes.DESELECT_ALL_FIELD;
} }
@@ -120,6 +138,7 @@ export class MetadataRegistryDeselectAllFieldAction implements Action {
/** /**
* Export a type alias of all actions in this action group * Export a type alias of all actions in this action group
* so that reducers can easily compose action types * so that reducers can easily compose action types
* These are all the actions to perform on the metadata registry state
*/ */
export type MetadataRegistryAction export type MetadataRegistryAction
= MetadataRegistryEditSchemaAction = MetadataRegistryEditSchemaAction

View File

@@ -1,35 +0,0 @@
/**
* Makes sure that if the user navigates to another route, the sidebar is collapsed
*/
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { filter, map, tap } from 'rxjs/operators';
import { SearchSidebarCollapseAction } from '../../../+search-page/search-sidebar/search-sidebar.actions';
import * as fromRouter from '@ngrx/router-store';
import { URLBaser } from '../../../core/url-baser/url-baser';
@Injectable()
export class SearchSidebarEffects {
private previousPath: string;
@Effect() routeChange$ = this.actions$
.pipe(
ofType(fromRouter.ROUTER_NAVIGATION),
filter((action) => this.previousPath !== this.getBaseUrl(action)),
tap((action) => {
this.previousPath = this.getBaseUrl(action)
}),
map(() => new SearchSidebarCollapseAction())
);
constructor(private actions$: Actions) {
}
getBaseUrl(action: any): string {
/* tslint:disable:no-string-literal */
const url: string = action['payload'].routerState.url;
return new URLBaser(url).toString();
/* tslint:enable:no-string-literal */
}
}

View File

@@ -0,0 +1,185 @@
import {
MetadataRegistryCancelFieldAction,
MetadataRegistryCancelSchemaAction, MetadataRegistryDeselectAllFieldAction,
MetadataRegistryDeselectAllSchemaAction, MetadataRegistryDeselectFieldAction,
MetadataRegistryDeselectSchemaAction, MetadataRegistryEditFieldAction,
MetadataRegistryEditSchemaAction, MetadataRegistrySelectFieldAction,
MetadataRegistrySelectSchemaAction
} from './metadata-registry.actions';
import { metadataRegistryReducer, MetadataRegistryState } from './metadata-registry.reducers';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
class NullAction extends MetadataRegistryEditSchemaAction {
type = null;
constructor() {
super(undefined);
}
}
const schema: MetadataSchema = Object.assign(new MetadataSchema(),
{
id: 'schema-id',
self: 'http://rest.self/schema/dc',
prefix: 'dc',
namespace: 'http://dublincore.org/documents/dcmi-terms/'
});
const schema2: MetadataSchema = Object.assign(new MetadataSchema(),
{
id: 'another-schema-id',
self: 'http://rest.self/schema/dcterms',
prefix: 'dcterms',
namespace: 'http://purl.org/dc/terms/'
});
const field: MetadataField = Object.assign(new MetadataField(),
{
id: 'author-field-id',
self: 'http://rest.self/field/author',
element: 'contributor',
qualifier: 'author',
scopeNote: 'Author of an item',
schema: schema
});
const field2: MetadataField = Object.assign(new MetadataField(),
{
id: 'title-field-id',
self: 'http://rest.self/field/title',
element: 'title',
qualifier: null,
scopeNote: 'Title of an item',
schema: schema
});
const initialState: MetadataRegistryState = {
editSchema: null,
selectedSchemas: [],
editField: null,
selectedFields: []
};
const editState: MetadataRegistryState = {
editSchema: schema,
selectedSchemas: [],
editField: field,
selectedFields: []
};
const selectState: MetadataRegistryState = {
editSchema: null,
selectedSchemas: [schema2],
editField: null,
selectedFields: [field2]
};
const moreSelectState: MetadataRegistryState = {
editSchema: null,
selectedSchemas: [schema, schema2],
editField: null,
selectedFields: [field, field2]
};
describe('metadataRegistryReducer', () => {
it('should return the current state when no valid actions have been made', () => {
const state = initialState;
const action = new NullAction();
const newState = metadataRegistryReducer(state, action);
expect(newState).toEqual(state);
});
it('should start with an the initial state', () => {
const state = initialState;
const action = new NullAction();
const initState = metadataRegistryReducer(undefined, action);
expect(initState).toEqual(state);
});
it('should update the current state to change the editSchema to a new schema when MetadataRegistryEditSchemaAction is dispatched', () => {
const state = editState;
const action = new MetadataRegistryEditSchemaAction(schema2);
const newState = metadataRegistryReducer(state, action);
expect(newState.editSchema).toEqual(schema2);
});
it('should update the current state to remove the editSchema from the state when MetadataRegistryCancelSchemaAction is dispatched', () => {
const state = editState;
const action = new MetadataRegistryCancelSchemaAction();
const newState = metadataRegistryReducer(state, action);
expect(newState.editSchema).toEqual(null);
});
it('should update the current state to add a given schema to the selectedSchemas when MetadataRegistrySelectSchemaAction is dispatched', () => {
const state = selectState;
const action = new MetadataRegistrySelectSchemaAction(schema);
const newState = metadataRegistryReducer(state, action);
expect(newState.selectedSchemas).toContain(schema);
expect(newState.selectedSchemas).toContain(schema2);
});
it('should update the current state to remove a given schema to the selectedSchemas when MetadataRegistryDeselectSchemaAction is dispatched', () => {
const state = selectState;
const action = new MetadataRegistryDeselectSchemaAction(schema2);
const newState = metadataRegistryReducer(state, action);
expect(newState.selectedSchemas).toEqual([]);
});
it('should update the current state to remove a given schema to the selectedSchemas when MetadataRegistryDeselectAllSchemaAction is dispatched', () => {
const state = selectState;
const action = new MetadataRegistryDeselectAllSchemaAction();
const newState = metadataRegistryReducer(state, action);
expect(newState.selectedSchemas).toEqual([]);
});
it('should update the current state to change the editField to a new field when MetadataRegistryEditFieldAction is dispatched', () => {
const state = editState;
const action = new MetadataRegistryEditFieldAction(field2);
const newState = metadataRegistryReducer(state, action);
expect(newState.editField).toEqual(field2);
});
it('should update the current state to remove the editField from the state when MetadataRegistryCancelFieldAction is dispatched', () => {
const state = editState;
const action = new MetadataRegistryCancelFieldAction();
const newState = metadataRegistryReducer(state, action);
expect(newState.editField).toEqual(null);
});
it('should update the current state to add a given field to the selectedFields when MetadataRegistrySelectFieldAction is dispatched', () => {
const state = selectState;
const action = new MetadataRegistrySelectFieldAction(field);
const newState = metadataRegistryReducer(state, action);
expect(newState.selectedFields).toContain(field);
expect(newState.selectedFields).toContain(field2);
});
it('should update the current state to remove a given field to the selectedFields when MetadataRegistryDeselectFieldAction is dispatched', () => {
const state = selectState;
const action = new MetadataRegistryDeselectFieldAction(field2);
const newState = metadataRegistryReducer(state, action);
expect(newState.selectedFields).toEqual([]);
});
it('should update the current state to remove a given field to the selectedFields when MetadataRegistryDeselectAllFieldAction is dispatched', () => {
const state = selectState;
const action = new MetadataRegistryDeselectAllFieldAction();
const newState = metadataRegistryReducer(state, action);
expect(newState.selectedFields).toEqual([]);
});
});

View File

@@ -12,8 +12,8 @@ import {
import { MetadataField } from '../../../core/metadata/metadatafield.model'; import { MetadataField } from '../../../core/metadata/metadatafield.model';
/** /**
* The auth state. * The metadata registry state.
* @interface State * @interface MetadataRegistryState
*/ */
export interface MetadataRegistryState { export interface MetadataRegistryState {
editSchema: MetadataSchema; editSchema: MetadataSchema;

View File

@@ -2,14 +2,12 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angu
import { MetadataSchema } from '../../../../core/metadata/metadataschema.model'; import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
import { import {
DynamicFormControlModel, DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormLayout, DynamicFormLayout,
DynamicInputModel DynamicInputModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { RegistryService } from '../../../../core/registry/registry.service'; import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { Observable } from 'rxjs/internal/Observable';
import { MetadataField } from '../../../../core/metadata/metadatafield.model'; import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';

View File

@@ -6,7 +6,6 @@ import {
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';

View File

@@ -0,0 +1,130 @@
import { first } from 'rxjs/operators';
import { BrowseByGuard } from './browse-by-guard';
import { of as observableOf } from 'rxjs';
describe('BrowseByGuard', () => {
describe('canActivate', () => {
let guard: BrowseByGuard;
let dsoService: any;
let translateService: any;
const name = 'An interesting DSO';
const title = 'Author';
const field = 'Author';
const metadata = 'author';
const metadataField = 'dc.contributor';
const scope = '1234-65487-12354-1235';
const value = 'Filter';
beforeEach(() => {
dsoService = {
findById: (id: string) => observableOf({ payload: { name: name }, hasSucceeded: true })
};
translateService = {
instant: () => field
};
guard = new BrowseByGuard(dsoService, translateService);
});
it('should return true, and sets up the data correctly, with a scope and value', () => {
const scopedRoute = {
data: {
title: field,
metadataField,
},
params: {
metadata,
},
queryParams: {
scope,
value
}
};
guard.canActivate(scopedRoute as any, undefined)
.pipe(first())
.subscribe(
(canActivate) => {
const result =
{
title,
metadata,
metadataField,
collection: name,
field,
value: '"' + value + '"'
};
expect(scopedRoute.data).toEqual(result);
expect(canActivate).toEqual(true);
}
);
});
it('should return true, and sets up the data correctly, with a scope and without value', () => {
const scopedNoValueRoute = {
data: {
title: field,
metadataField,
},
params: {
metadata,
},
queryParams: {
scope
}
};
guard.canActivate(scopedNoValueRoute as any, undefined)
.pipe(first())
.subscribe(
(canActivate) => {
const result =
{
title,
metadata,
metadataField,
collection: name,
field,
value: ''
};
expect(scopedNoValueRoute.data).toEqual(result);
expect(canActivate).toEqual(true);
}
);
});
it('should return true, and sets up the data correctly, without a scope and with a value', () => {
const route = {
data: {
title: field,
metadataField,
},
params: {
metadata,
},
queryParams: {
value
}
};
guard.canActivate(route as any, undefined)
.pipe(first())
.subscribe(
(canActivate) => {
const result =
{
title,
metadata,
metadataField,
collection: '',
field,
value: '"' + value + '"'
};
expect(route.data).toEqual(result);
expect(canActivate).toEqual(true);
}
);
});
});
});

View File

@@ -2,10 +2,10 @@ import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angul
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { combineLatest as observableCombineLatest } from 'rxjs'; import { map } from 'rxjs/operators';
import { map, take } from 'rxjs/operators';
import { getSucceededRemoteData } from '../core/shared/operators'; import { getSucceededRemoteData } from '../core/shared/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
@Injectable() @Injectable()
/** /**
@@ -19,29 +19,23 @@ export class BrowseByGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const title = route.data.title; const title = route.data.title;
const metadata = route.params.metadata || route.queryParams.metadata || route.data.metadata; const metadata = route.params.metadata || route.queryParams.metadata || route.data.metadata;
const metadataField = route.data.metadataField; const metadataField = route.data.metadataField;
const scope = route.queryParams.scope; const scope = route.queryParams.scope;
const value = route.queryParams.value; const value = route.queryParams.value;
const metadataTranslated = this.translate.instant('browse.metadata.' + metadata);
const metadataTranslated$ = this.translate.get('browse.metadata.' + metadata).pipe(take(1));
if (hasValue(scope)) { if (hasValue(scope)) {
const dsoAndMetadata$ = observableCombineLatest(metadataTranslated$, this.dsoService.findById(scope).pipe(getSucceededRemoteData())); const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getSucceededRemoteData());
return dsoAndMetadata$.pipe( return dsoAndMetadata$.pipe(
map(([metadataTranslated, dsoRD]) => { map((dsoRD) => {
const name = dsoRD.payload.name; const name = dsoRD.payload.name;
route.data = this.createData(title, metadata, metadataField, name, metadataTranslated, value);; route.data = this.createData(title, metadata, metadataField, name, metadataTranslated, value);
return true; return true;
}) })
); );
} else { } else {
return metadataTranslated$.pipe( route.data = this.createData(title, metadata, metadataField, '', metadataTranslated, value);
map((metadataTranslated: string) => { return observableOf(true);
route.data = this.createData(title, metadata, metadataField, '', metadataTranslated, value);
return true;
})
)
} }
} }

View File

@@ -0,0 +1,32 @@
import { first } from 'rxjs/operators';
import { of as observableOf } from 'rxjs';
import { CollectionPageResolver } from './collection-page.resolver';
describe('CollectionPageResolver', () => {
describe('resolve', () => {
let resolver: CollectionPageResolver;
let collectionService: any;
const id = '1234-65487-12354-1235';
beforeEach(() => {
collectionService = {
findById: (id: string) => observableOf({ payload: { id }, hasSucceeded: true })
};
resolver = new CollectionPageResolver(collectionService);
});
it('should resolve a collection with the correct id', () => {
resolver.resolve({ params: { id } } as any, undefined)
.pipe(first())
.subscribe(
(resolved) => {
expect(resolved.payload.id).toEqual(id);
}
);
});
});
});

View File

@@ -21,11 +21,19 @@ import { hasValue } from '../shared/empty.util';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations: [fadeInOut] animations: [fadeInOut]
}) })
export class CommunityPageComponent implements OnInit, OnDestroy { /**
* This component represents a detail page for a single community
*/
export class CommunityPageComponent implements OnInit {
/**
* The community displayed on this page
*/
communityRD$: Observable<RemoteData<Community>>; communityRD$: Observable<RemoteData<Community>>;
logoRD$: Observable<RemoteData<Bitstream>>;
private subs: Subscription[] = [];
/**
* The logo of this community
*/
logoRD$: Observable<RemoteData<Bitstream>>;
constructor( constructor(
private communityDataService: CommunityDataService, private communityDataService: CommunityDataService,
private metadata: MetadataService, private metadata: MetadataService,
@@ -42,7 +50,4 @@ export class CommunityPageComponent implements OnInit, OnDestroy {
mergeMap((community: Community) => community.logo)); mergeMap((community: Community) => community.logo));
} }
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
} }