diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 6ba4e8146b..bc0cbb8da6 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -6,13 +6,24 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +/** + * This component renders a list of bitstream formats + */ @Component({ selector: 'ds-bitstream-formats', templateUrl: './bitstream-formats.component.html' }) export class BitstreamFormatsComponent { + /** + * A paginated list of bitstream formats to be shown on the page + */ bitstreamFormats: Observable>>; + + /** + * The current pagination configuration for the page + * Currently simply renders all bitstream formats + */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { id: 'registry-bitstreamformats-pagination', pageSize: 10000 @@ -22,11 +33,18 @@ export class BitstreamFormatsComponent { 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) { this.config.currentPage = event; this.updateFormats(); } + /** + * Method to update the bitstream formats that are shown + */ private updateFormats() { this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config); } diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.actions.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.actions.ts index 92d4a7a72a..7358123462 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.actions.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.actions.ts @@ -28,7 +28,7 @@ export const MetadataRegistryActionTypes = { /* 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 { 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 { type = MetadataRegistryActionTypes.CANCEL_EDIT_SCHEMA; } +/** + * Used to select a single metadata schema in the metadata registry + */ export class MetadataRegistrySelectSchemaAction implements Action { 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 { 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 { 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 { 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 { type = MetadataRegistryActionTypes.CANCEL_EDIT_FIELD; } +/** + * Used to select a single metadata field in the metadata registry + */ export class MetadataRegistrySelectFieldAction implements Action { 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 { 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 { 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 * so that reducers can easily compose action types + * These are all the actions to perform on the metadata registry state */ export type MetadataRegistryAction = MetadataRegistryEditSchemaAction diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.effects.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.effects.ts deleted file mode 100644 index 32aa2d27d7..0000000000 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.effects.ts +++ /dev/null @@ -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 */ - } - -} diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.spec.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.spec.ts new file mode 100644 index 0000000000..392440ae86 --- /dev/null +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.spec.ts @@ -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([]); + }); +}); diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.ts index f335c880ae..d20e3d5bcc 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.reducers.ts @@ -12,8 +12,8 @@ import { import { MetadataField } from '../../../core/metadata/metadatafield.model'; /** - * The auth state. - * @interface State + * The metadata registry state. + * @interface MetadataRegistryState */ export interface MetadataRegistryState { editSchema: MetadataSchema; diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 4c09428bd5..2da2b45021 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -2,14 +2,12 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angu import { MetadataSchema } from '../../../../core/metadata/metadataschema.model'; import { DynamicFormControlModel, - DynamicFormGroupModel, DynamicFormLayout, DynamicInputModel } from '@ng-dynamic-forms/core'; import { FormGroup } from '@angular/forms'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { Observable } from 'rxjs/internal/Observable'; import { MetadataField } from '../../../../core/metadata/metadatafield.model'; import { take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts index 29a3f9ad31..2acd96adb0 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts @@ -6,7 +6,6 @@ import { import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import { RemoteData } from '../../core/data/remote-data'; -import { PaginatedList } from '../../core/data/paginated-list'; import { Item } from '../../core/shared/item.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ActivatedRoute, Router } from '@angular/router'; diff --git a/src/app/+browse-by/browse-by-guard.spec.ts b/src/app/+browse-by/browse-by-guard.spec.ts new file mode 100644 index 0000000000..ed5d7cdd31 --- /dev/null +++ b/src/app/+browse-by/browse-by-guard.spec.ts @@ -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); + } + ); + }); + }); +}); diff --git a/src/app/+browse-by/browse-by-guard.ts b/src/app/+browse-by/browse-by-guard.ts index 30f5d69ffc..5d3dad2b0f 100644 --- a/src/app/+browse-by/browse-by-guard.ts +++ b/src/app/+browse-by/browse-by-guard.ts @@ -2,10 +2,10 @@ import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angul import { Injectable } from '@angular/core'; import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service'; import { hasValue } from '../shared/empty.util'; -import { combineLatest as observableCombineLatest } from 'rxjs'; -import { map, take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { getSucceededRemoteData } from '../core/shared/operators'; import { TranslateService } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; @Injectable() /** @@ -19,29 +19,23 @@ export class BrowseByGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 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 scope = route.queryParams.scope; const value = route.queryParams.value; - - const metadataTranslated$ = this.translate.get('browse.metadata.' + metadata).pipe(take(1)); - + const metadataTranslated = this.translate.instant('browse.metadata.' + metadata); if (hasValue(scope)) { - const dsoAndMetadata$ = observableCombineLatest(metadataTranslated$, this.dsoService.findById(scope).pipe(getSucceededRemoteData())); + const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getSucceededRemoteData()); return dsoAndMetadata$.pipe( - map(([metadataTranslated, dsoRD]) => { + map((dsoRD) => { 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; }) ); } else { - return metadataTranslated$.pipe( - map((metadataTranslated: string) => { - route.data = this.createData(title, metadata, metadataField, '', metadataTranslated, value); - return true; - }) - ) + route.data = this.createData(title, metadata, metadataField, '', metadataTranslated, value); + return observableOf(true); } } diff --git a/src/app/+collection-page/collection-page.resolver.spec.ts b/src/app/+collection-page/collection-page.resolver.spec.ts new file mode 100644 index 0000000000..8213811694 --- /dev/null +++ b/src/app/+collection-page/collection-page.resolver.spec.ts @@ -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); + } + ); + }); + + }); +}); diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 0483143230..2035faf988 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -21,11 +21,19 @@ import { hasValue } from '../shared/empty.util'; changeDetection: ChangeDetectionStrategy.OnPush, 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>; - logoRD$: Observable>; - private subs: Subscription[] = []; + /** + * The logo of this community + */ + logoRD$: Observable>; constructor( private communityDataService: CommunityDataService, private metadata: MetadataService, @@ -42,7 +50,4 @@ export class CommunityPageComponent implements OnInit, OnDestroy { mergeMap((community: Community) => community.logo)); } - ngOnDestroy(): void { - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); - } }