From 8125f56009cff1fca29fc6da5a8497ba07845fd6 Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 27 Feb 2019 13:18:35 +0100 Subject: [PATCH] finished docs and tests --- .../edit-in-place-field.component.spec.ts | 1 - .../item-metadata.component.spec.ts | 49 ++++++++++++++++--- .../item-metadata/item-metadata.component.ts | 2 +- src/app/core/eperson/models/eperson.model.ts | 1 + src/app/core/shared/dspace-object.model.ts | 12 ++++- src/app/core/shared/metadata.models.ts | 9 ++-- ...a.model.spec.ts => metadata.utils.spec.ts} | 0 src/app/core/shared/metadata.utils.ts | 48 ++++++++++++------ .../comcol-form/comcol-form.component.ts | 8 +-- 9 files changed, 98 insertions(+), 32 deletions(-) rename src/app/core/shared/{metadata.model.spec.ts => metadata.utils.spec.ts} (100%) diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts index 02e4bf3413..09363b9964 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts @@ -60,7 +60,6 @@ describe('EditInPlaceFieldComponent', () => { metadataFieldService = jasmine.createSpyObj({ queryMetadataFields: observableOf(new RemoteData(false, false, true, undefined, paginatedMetadataFields)), - getAllMetadataFields: observableOf(new RemoteData(false, false, true, undefined, paginatedMetadataFields)) }); objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', { diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts index c1d36c70fa..674e41e13f 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CUSTOM_ELEMENTS_SCHEMA, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { of as observableOf } from 'rxjs'; import { getTestScheduler } from 'jasmine-marbles'; import { ItemMetadataComponent } from './item-metadata.component'; @@ -22,6 +22,11 @@ import { Item } from '../../../core/shared/item.model'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; import { RemoteData } from '../../../core/data/remote-data'; import { MetadatumViewModel } from '../../../core/shared/metadata.models'; +import { RegistryService } from '../../../core/registry/registry.service'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { MetadataSchema } from '../../../core/metadata/metadataschema.model'; +import { MetadataField } from '../../../core/metadata/metadatafield.model'; +import { Metadata } from '../../../core/shared/metadata.utils'; let comp: ItemMetadataComponent; let fixture: ComponentFixture; @@ -33,8 +38,23 @@ const warningNotification: INotification = new Notification('id', NotificationTy const successNotification: INotification = new Notification('id', NotificationType.Success, 'success'); const date = new Date(); const router = new RouterStub(); +let metadataFieldService; +let paginatedMetadataFields; let routeStub; +const mdSchema = Object.assign(new MetadataSchema(), { prefix: 'dc' }); +const mdField1 = Object.assign(new MetadataField(), { + schema: mdSchema, + element: 'contributor', + qualifier: 'author' +}); +const mdField2 = Object.assign(new MetadataField(), { schema: mdSchema, element: 'title' }); +const mdField3 = Object.assign(new MetadataField(), { + schema: mdSchema, + element: 'description', + qualifier: 'abstract' +}); + let itemService; const notificationsService = jasmine.createSpyObj('notificationsService', { @@ -83,7 +103,18 @@ let scheduler: TestScheduler; let item; describe('ItemMetadataComponent', () => { beforeEach(async(() => { - item = Object.assign(new Item(), { metadata: [metadatum1, metadatum2, metadatum3] }, { lastModified: date }); + item = Object.assign(new Item(), { + metadata: { + [metadatum1.key]: [metadatum1], + [metadatum2.key]: [metadatum2], + [metadatum3.key]: [metadatum3] + } + }, + { + lastModified: date + } + ) + ; itemService = jasmine.createSpyObj('itemService', { update: observableOf(new RemoteData(false, false, true, undefined, item)), commitUpdates: {} @@ -93,6 +124,11 @@ describe('ItemMetadataComponent', () => { data: observableOf({ item: new RemoteData(false, false, true, null, item) }) } }; + paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]); + + metadataFieldService = jasmine.createSpyObj({ + getAllMetadataFields: observableOf(new RemoteData(false, false, true, undefined, paginatedMetadataFields)) + }); scheduler = getTestScheduler(); objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', { @@ -122,7 +158,8 @@ describe('ItemMetadataComponent', () => { { provide: Router, useValue: router }, { provide: ActivatedRoute, useValue: routeStub }, { provide: NotificationsService, useValue: notificationsService }, - { provide: GLOBAL_CONFIG, useValue: { notifications: { timeOut: 10 } } as any } + { provide: GLOBAL_CONFIG, useValue: { notifications: { timeOut: 10 } } as any }, + { provide: RegistryService, useValue: metadataFieldService }, ], schemas: [ NO_ERRORS_SCHEMA ] @@ -176,9 +213,9 @@ describe('ItemMetadataComponent', () => { }); it('it should call reinstateFieldUpdates on the objectUpdatesService with the correct url and metadata', () => { - expect(objectUpdatesService.getUpdatedFields).toHaveBeenCalledWith(url, comp.item.metadata); - expect(itemService.update).toHaveBeenCalledWith(comp.item); - expect(objectUpdatesService.getFieldUpdates).toHaveBeenCalledWith(url, comp.item.metadata); + expect(objectUpdatesService.getUpdatedFields).toHaveBeenCalledWith(url, comp.item.metadataAsList); + expect(itemService.update).toHaveBeenCalledWith(Object.assign(comp.item, { metadata: Metadata.toMetadataMap(comp.item.metadataAsList) })); + expect(objectUpdatesService.getFieldUpdates).toHaveBeenCalledWith(url, comp.item.metadataAsList); }); }); diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts index fb6820d5fd..1d5c1064ba 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -72,7 +72,7 @@ export class ItemMetadataComponent implements OnInit { * Set up and initialize all fields */ ngOnInit(): void { - this.metadataFields$ = this.findMetadataFields() + this.metadataFields$ = this.findMetadataFields(); this.route.parent.data.pipe(map((data) => data.item)) .pipe( first(), diff --git a/src/app/core/eperson/models/eperson.model.ts b/src/app/core/eperson/models/eperson.model.ts index 0218e52c7d..7d2138b633 100644 --- a/src/app/core/eperson/models/eperson.model.ts +++ b/src/app/core/eperson/models/eperson.model.ts @@ -19,6 +19,7 @@ export class EPerson extends DSpaceObject { public selfRegistered: boolean; + /** Getter to retrieve the EPerson's full name as a string */ get name(): string { return this.firstMetadataValue('eperson.firstname') + ' ' + this.firstMetadataValue('eperson.lastname'); } diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts index b23de0da90..a2f5f721a4 100644 --- a/src/app/core/shared/dspace-object.model.ts +++ b/src/app/core/shared/dspace-object.model.ts @@ -1,4 +1,9 @@ -import { MetadataMap, MetadataValue, MetadataValueFilter } from './metadata.models'; +import { + MetadataMap, + MetadataValue, + MetadataValueFilter, + MetadatumViewModel +} from './metadata.models'; import { Metadata } from './metadata.utils'; import { CacheableObject } from '../cache/object-cache.reducer'; import { RemoteData } from '../data/remote-data'; @@ -40,7 +45,10 @@ export class DSpaceObject implements CacheableObject, ListableObject { */ metadata: MetadataMap; - get metadataAsList() { + /** + * Retrieve the current metadata as a list of MetadatumViewModels + */ + get metadataAsList(): MetadatumViewModel[] { return Metadata.toViewModelList(this.metadata); } diff --git a/src/app/core/shared/metadata.models.ts b/src/app/core/shared/metadata.models.ts index 9ff08d3c3d..b72eb340d3 100644 --- a/src/app/core/shared/metadata.models.ts +++ b/src/app/core/shared/metadata.models.ts @@ -1,5 +1,6 @@ import * as uuidv4 from 'uuid/v4'; import { autoserialize, Serialize, Deserialize } from 'cerialize'; +/* tslint:disable:max-classes-per-file */ /** A map of metadata keys to an ordered list of MetadataValue objects. */ export class MetadataMap { @@ -9,7 +10,6 @@ export class MetadataMap { /** A single metadata value and its properties. */ export class MetadataValue { - /** The uuid. */ uuid: string = uuidv4(); @@ -20,7 +20,6 @@ export class MetadataValue { /** The string value. */ @autoserialize value: string; - } /** Constraints for matching metadata values. */ @@ -55,7 +54,10 @@ export class MetadatumViewModel { order: number; } - +/** Serializer used for MetadataMaps. + * This is necessary because Cerialize has trouble instantiating the MetadataValues using their constructor + * when they are inside arrays which also represent the values in a map. + */ export const MetadataMapSerializer = { Serialize(map: MetadataMap): any { const json = {}; @@ -73,3 +75,4 @@ export const MetadataMapSerializer = { return metadataMap; } }; +/* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/shared/metadata.model.spec.ts b/src/app/core/shared/metadata.utils.spec.ts similarity index 100% rename from src/app/core/shared/metadata.model.spec.ts rename to src/app/core/shared/metadata.utils.spec.ts diff --git a/src/app/core/shared/metadata.utils.ts b/src/app/core/shared/metadata.utils.ts index d7cca987d6..1e9446912d 100644 --- a/src/app/core/shared/metadata.utils.ts +++ b/src/app/core/shared/metadata.utils.ts @@ -149,7 +149,7 @@ export class Metadata { * @param {MetadataMap} mdMap The source map. * @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see above. */ - private static resolveKeys(mdMap: MetadataMap, keyOrKeys: string | string[]): string[] { + private static resolveKeys(mdMap: MetadataMap = {}, keyOrKeys: string | string[]): string[] { const inputKeys: string[] = keyOrKeys instanceof Array ? keyOrKeys : [keyOrKeys]; const outputKeys: string[] = []; for (const inputKey of inputKeys) { @@ -167,33 +167,51 @@ export class Metadata { return outputKeys; } - public static toViewModelList(mdMap: MetadataMap) { + /** + * Creates an array of MetadatumViewModels from an existing MetadataMap. + * + * @param {MetadataMap} mdMap The source map. + * @returns {MetadatumViewModel[]} List of metadata view models based on the source map. + */ + public static toViewModelList(mdMap: MetadataMap): MetadatumViewModel[] { let metadatumList: MetadatumViewModel[] = []; Object.keys(mdMap) .sort() .forEach((key: string) => { - const fields = mdMap[key].map( - (metadataValue: MetadataValue, index: number) => - Object.assign( - {}, - metadataValue, - { - order: index, - key - })); - metadatumList = [...metadatumList, ...fields]; - }); + const fields = mdMap[key].map( + (metadataValue: MetadataValue, index: number) => + Object.assign( + {}, + metadataValue, + { + order: index, + key + })); + metadatumList = [...metadatumList, ...fields]; + }); return metadatumList; } - public static toMetadataMap(viewModelList: MetadatumViewModel[]) { + /** + * Creates an MetadataMap from an existing array of MetadatumViewModels. + * + * @param {MetadatumViewModel[]} viewModelList The source list. + * @returns {MetadataMap} Map with metadata values based on the source list. + */ + public static toMetadataMap(viewModelList: MetadatumViewModel[]): MetadataMap { const metadataMap: MetadataMap = {}; const groupedList = groupBy(viewModelList, (viewModel) => viewModel.key); Object.keys(groupedList) .sort() .forEach((key: string) => { const orderedValues = sortBy(groupedList[key], ['order']); - metadataMap[key] = orderedValues.map((value: MetadataValue, index: number) => Object.assign({}, value, { order: index })) + metadataMap[key] = orderedValues.map((value: MetadataValue) => { + const val = Object.assign({}, value); + delete (val as any).order; + delete (val as any).key; + return val; + } + ) }); return metadataMap; } diff --git a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts index f8263fab73..e24676a646 100644 --- a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts @@ -83,10 +83,10 @@ export class ComColFormComponent implements OnInit { onSubmit() { const formMetadata = new Object() as MetadataMap; this.formModel.forEach((fieldModel: DynamicInputModel) => { - const value: MetadataValue = Object.assign(new MetadataValue(), { - value: fieldModel.value as string, - language: null - }); + const value: MetadataValue = { + value: fieldModel.value as string, + language: null + } as any; if (formMetadata.hasOwnProperty(fieldModel.name)) { formMetadata[fieldModel.name].push(value); } else {