mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
fixed and tests for equals method
This commit is contained in:
@@ -5,7 +5,7 @@ import { ListableObject } from '../shared/object-collection/shared/listable-obje
|
||||
/**
|
||||
* Represents a search result object of a certain (<T>) DSpaceObject
|
||||
*/
|
||||
export class MyDSpaceResult<T extends DSpaceObject> implements ListableObject {
|
||||
export class MyDSpaceResult<T extends DSpaceObject> extends ListableObject {
|
||||
/**
|
||||
* The DSpaceObject that was found
|
||||
*/
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
|
||||
import { EPerson } from './eperson.model';
|
||||
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
@@ -9,7 +8,7 @@ import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(EPerson)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
export class NormalizedEPerson extends NormalizedDSpaceObject<EPerson> implements CacheableObject, ListableObject {
|
||||
export class NormalizedEPerson extends NormalizedDSpaceObject<EPerson> implements CacheableObject {
|
||||
|
||||
/**
|
||||
* A string representing the unique handle of this EPerson
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
|
||||
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
import { Group } from './group.model';
|
||||
@@ -9,7 +8,7 @@ import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Group)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
export class NormalizedGroup extends NormalizedDSpaceObject<Group> implements CacheableObject, ListableObject {
|
||||
export class NormalizedGroup extends NormalizedDSpaceObject<Group> implements CacheableObject {
|
||||
|
||||
/**
|
||||
* List of Groups that this Group belong to
|
||||
@@ -36,3 +35,4 @@ export class NormalizedGroup extends NormalizedDSpaceObject<Group> implements Ca
|
||||
@autoserialize
|
||||
public permanent: boolean;
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import { autoserialize } from 'cerialize';
|
||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
|
||||
export class MetadataField implements ListableObject {
|
||||
export class MetadataField extends ListableObject {
|
||||
@autoserialize
|
||||
id: number;
|
||||
|
||||
|
@@ -1,16 +1,13 @@
|
||||
import { autoserialize } from 'cerialize';
|
||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
|
||||
export class MetadataSchema implements ListableObject {
|
||||
@autoserialize
|
||||
export class MetadataSchema extends ListableObject {
|
||||
id: number;
|
||||
|
||||
@autoserialize
|
||||
self: string;
|
||||
|
||||
@autoserialize
|
||||
prefix: string;
|
||||
|
||||
@autoserialize
|
||||
namespace: string;
|
||||
}
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import { autoserialize } from 'cerialize';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
import { mapsTo } from '../cache/builders/build-decorators';
|
||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||
import { MetadataSchema } from './metadataschema.model';
|
||||
|
||||
/**
|
||||
* Normalized class for a DSpace MetadataSchema
|
||||
*/
|
||||
@mapsTo(MetadataSchema)
|
||||
export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> implements ListableObject {
|
||||
export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> {
|
||||
/**
|
||||
* The unique identifier for this schema
|
||||
*/
|
||||
@@ -32,4 +31,5 @@ export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> i
|
||||
*/
|
||||
@autoserialize
|
||||
namespace: string;
|
||||
|
||||
}
|
||||
|
@@ -400,7 +400,7 @@ export class RegistryService {
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
const serializedSchema = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(ResourceType.MetadataSchema)).serialize(schema as NormalizedMetadataSchema);
|
||||
const serializedSchema = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(ResourceType.MetadataSchema)).serialize(schema as any as NormalizedMetadataSchema);
|
||||
|
||||
const request$ = endpoint$.pipe(
|
||||
take(1),
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { autoserialize, autoserializeAs } from 'cerialize';
|
||||
import { Equatable } from '../utilities/equatable';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||
|
||||
export class BrowseEntry implements Equatable<BrowseEntry> {
|
||||
export class BrowseEntry extends ListableObject {
|
||||
@autoserialize
|
||||
type: string;
|
||||
|
||||
@@ -19,11 +19,4 @@ export class BrowseEntry implements Equatable<BrowseEntry> {
|
||||
@autoserialize
|
||||
count: number;
|
||||
|
||||
equals(other: BrowseEntry): boolean {
|
||||
if (hasValue(other)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { hasNoValue } from '../../shared/empty.util';
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
*/
|
||||
export class DSpaceObject implements CacheableObject, ListableObject {
|
||||
export class DSpaceObject extends ListableObject implements CacheableObject {
|
||||
|
||||
private _name: string;
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { SearchSidebarService } from './search-sidebar.service';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from '../../../shared/search/search-sidebar/search-sidebar.actions';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { AppState } from '../../../app.reducer';
|
||||
import { HostWindowService } from '../../../shared/host-window.service';
|
||||
|
||||
describe('SearchSidebarService', () => {
|
||||
let service: SearchSidebarService;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||
import { EPerson } from '../../eperson/models/eperson.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
@@ -18,7 +17,7 @@ export interface SubmissionObjectError {
|
||||
/**
|
||||
* An abstract model class for a SubmissionObject.
|
||||
*/
|
||||
export abstract class SubmissionObject extends DSpaceObject implements CacheableObject, ListableObject {
|
||||
export abstract class SubmissionObject extends DSpaceObject implements CacheableObject {
|
||||
|
||||
/**
|
||||
* The workspaceitem/workflowitem identifier
|
||||
|
@@ -2,14 +2,13 @@ import { Observable } from 'rxjs';
|
||||
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Workflowitem } from '../../submission/models/workflowitem.model';
|
||||
|
||||
/**
|
||||
* An abstract model class for a TaskObject.
|
||||
*/
|
||||
export class TaskObject extends DSpaceObject implements CacheableObject, ListableObject {
|
||||
export class TaskObject extends DSpaceObject implements CacheableObject {
|
||||
|
||||
/**
|
||||
* The task identifier
|
||||
|
@@ -34,6 +34,6 @@ export function fieldsForEquals(...fields: string[]): any {
|
||||
|
||||
|
||||
export function getFieldsForEquals(constructor: Function, field: string) {
|
||||
const fieldMap = excludedFromEquals.get(constructor) || new Map();
|
||||
const fieldMap = fieldsForEqualsMap.get(constructor) || new Map();
|
||||
return fieldMap.get(field);
|
||||
}
|
70
src/app/core/utilities/equatable.spec.ts
Normal file
70
src/app/core/utilities/equatable.spec.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { excludeFromEquals, fieldsForEquals } from './equals.decorators';
|
||||
import { EquatableObject } from './equatable';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
class Dog extends EquatableObject<Dog> {
|
||||
public name: string;
|
||||
|
||||
@excludeFromEquals
|
||||
public ballsCaught: number;
|
||||
|
||||
@fieldsForEquals('name', 'age')
|
||||
public owner: {
|
||||
name: string;
|
||||
age: number;
|
||||
favouriteFood: string;
|
||||
}
|
||||
}
|
||||
|
||||
fdescribe('equatable', () => {
|
||||
let dogRoger: Dog;
|
||||
let dogMissy: Dog;
|
||||
|
||||
beforeEach(() => {
|
||||
dogRoger = new Dog();
|
||||
dogRoger.name = 'Roger';
|
||||
dogRoger.ballsCaught = 6;
|
||||
dogRoger.owner = { name: 'Tommy', age: 16, favouriteFood: 'spaghetti' };
|
||||
|
||||
dogMissy = new Dog();
|
||||
dogMissy.name = 'Missy';
|
||||
dogMissy.ballsCaught = 9;
|
||||
dogMissy.owner = { name: 'Jenny', age: 29, favouriteFood: 'pizza' };
|
||||
});
|
||||
|
||||
it('should return false when the other object is undefined', () => {
|
||||
const isEqual = dogRoger.equals(undefined);
|
||||
expect(isEqual).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when the other object is the exact same object', () => {
|
||||
const isEqual = dogRoger.equals(dogRoger);
|
||||
expect(isEqual).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when the other object is an exact copy of the first one', () => {
|
||||
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||
expect(isEqual).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when the other object differs in all fields', () => {
|
||||
const isEqual = dogRoger.equals(dogMissy);
|
||||
expect(isEqual).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when the other object only differs in fields that are marked as excludeFromEquals', () => {
|
||||
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||
copyOfDogRoger.ballsCaught = 4;
|
||||
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||
expect(isEqual).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when the other object differs in fields that are not marked as excludeFromEquals', () => {
|
||||
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||
copyOfDogRoger.name = 'Elliot';
|
||||
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||
expect(isEqual).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@@ -12,11 +12,11 @@ function equalsByFields(object1, object2, fieldList): boolean {
|
||||
if (hasNoValue(object1[key]) || hasNoValue(object2[key])) {
|
||||
return true;
|
||||
}
|
||||
const mapping = getFieldsForEquals(this.constructor, key);
|
||||
const mapping = getFieldsForEquals(object1.constructor, key);
|
||||
if (hasValue(mapping)) {
|
||||
return !equalsByFields(object1[key], object2[key], mapping);
|
||||
}
|
||||
if (this[key] instanceof EquatableObject) {
|
||||
if (object1[key] instanceof EquatableObject) {
|
||||
return !object1[key].equals(object2[key]);
|
||||
}
|
||||
return object1[key] !== object2[key];
|
||||
@@ -26,6 +26,12 @@ function equalsByFields(object1, object2, fieldList): boolean {
|
||||
|
||||
export abstract class EquatableObject<T> {
|
||||
equals(other: T): boolean {
|
||||
if (hasNoValue(other)) {
|
||||
return false;
|
||||
}
|
||||
if (this as any === other) {
|
||||
return true;
|
||||
}
|
||||
const excludedKeys = getExcludedFromEqualsFor(this.constructor);
|
||||
const keys = Object.keys(this).filter((key) => excludedKeys.findIndex((excludedKey) => key === excludedKey) < 0);
|
||||
return equalsByFields(this, other, keys);
|
||||
|
@@ -8,15 +8,14 @@ import { PaginatedSearchOptions } from '../../../../../search/paginated-search-o
|
||||
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
|
||||
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { hasNoValue, hasValue, isNotEmpty } from '../../../../../empty.util';
|
||||
import { getSucceededRemoteData } from '../../../../../../core/shared/operators';
|
||||
import { concat, map, multicast, take, takeWhile, tap } from 'rxjs/operators';
|
||||
import { hasValue } from '../../../../../empty.util';
|
||||
import { concat, map, multicast, take, takeWhile } from 'rxjs/operators';
|
||||
import { Router } from '@angular/router';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||
import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer';
|
||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||
|
||||
const RELATION_TYPE_FILTER_PREFIX = 'f.entityType=';
|
||||
|
||||
|
@@ -0,0 +1,3 @@
|
||||
import { EquatableObject } from '../../../core/utilities/equatable';
|
||||
|
||||
export class ListableObject extends EquatableObject<ListableObject>{}
|
@@ -26,10 +26,12 @@ export class TypedItemSearchResultListElementComponent extends SearchResultListE
|
||||
this.object = obj as ItemSearchResult;
|
||||
this.dso = this.object.indexableObject;
|
||||
} else {
|
||||
this.object = {
|
||||
indexableObject: obj as Item,
|
||||
hitHighlights: new MetadataMap()
|
||||
};
|
||||
this.object = Object.assign(
|
||||
new ItemSearchResult(),
|
||||
{
|
||||
indexableObject: obj as Item,
|
||||
hitHighlights: new MetadataMap()
|
||||
});
|
||||
this.dso = obj as Item;
|
||||
}
|
||||
this.item = this.dso;
|
||||
|
@@ -80,13 +80,13 @@ function selectSingle(state: SelectableListState, action: SelectableListSelectSi
|
||||
}
|
||||
|
||||
function deselect(state: SelectableListState, action: SelectableListDeselectAction) {
|
||||
const newSelection = state.selection.filter((selected) => hasNoValue(action.payload.find((object) => object.uuid === selected.uuid)));
|
||||
const newSelection = state.selection.filter((selected) => hasNoValue(action.payload.find((object) => object.equals(selected))));
|
||||
return Object.assign({}, state, { selection: newSelection });
|
||||
}
|
||||
|
||||
function deselectSingle(state: SelectableListState, action: SelectableListDeselectSingleAction) {
|
||||
const newSelection = state.selection.filter((selected) => {
|
||||
return selected.uuid !== action.payload.uuid
|
||||
return !selected.equals(action.payload);
|
||||
});
|
||||
return Object.assign({}, state, { selection: newSelection });
|
||||
}
|
||||
@@ -101,5 +101,5 @@ function clearSelection(id: string) {
|
||||
|
||||
|
||||
function isObjectInSelection(selection: ListableObject[], object: ListableObject) {
|
||||
return selection.findIndex((selected) => selected.uuid === object.uuid) >= 0
|
||||
return selection.findIndex((selected) => selected.equals(object)) >= 0
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode
|
||||
/**
|
||||
* Represents a normalized version of a search result object of a certain DSpaceObject
|
||||
*/
|
||||
export class NormalizedSearchResult implements ListableObject {
|
||||
export class NormalizedSearchResult extends ListableObject {
|
||||
/**
|
||||
* The UUID of the DSpaceObject that was found
|
||||
*/
|
||||
|
@@ -2,15 +2,15 @@ import { SearchLabelsComponent } from './search-labels.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchService } from '../search-service/search.service';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Params } from '@angular/router';
|
||||
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service-stub';
|
||||
import { ObjectKeysPipe } from '../../utils/object-keys-pipe';
|
||||
import { SearchServiceStub } from '../../testing/search-service-stub';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service-stub';
|
||||
|
||||
describe('SearchLabelsComponent', () => {
|
||||
let comp: SearchLabelsComponent;
|
||||
|
@@ -5,7 +5,7 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode
|
||||
/**
|
||||
* Represents a search result object of a certain (<T>) DSpaceObject
|
||||
*/
|
||||
export class SearchResult<T extends DSpaceObject> implements ListableObject {
|
||||
export class SearchResult<T extends DSpaceObject> extends ListableObject {
|
||||
/**
|
||||
* The DSpaceObject that was found
|
||||
*/
|
||||
|
@@ -6,13 +6,16 @@ import { of as observableOf } from 'rxjs';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { SearchSwitchConfigurationComponent } from './search-switch-configuration.component';
|
||||
import { MYDSPACE_ROUTE, SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service-stub';
|
||||
import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
|
||||
import { NavigationExtras, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../shared/testing/router-stub';
|
||||
import { MyDSpaceConfigurationValueType } from '../../+my-dspace-page/my-dspace-configuration-value-type';
|
||||
import { SearchService } from '../search-service/search.service';
|
||||
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service-stub';
|
||||
import { RouterStub } from '../../testing/router-stub';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import {
|
||||
MYDSPACE_ROUTE,
|
||||
SEARCH_CONFIG_SERVICE
|
||||
} from '../../../+my-dspace-page/my-dspace-page.component';
|
||||
import { MyDSpaceConfigurationValueType } from '../../../+my-dspace-page/my-dspace-configuration-value-type';
|
||||
import { MockTranslateLoader } from '../../mocks/mock-translate-loader';
|
||||
|
||||
describe('SearchSwitchConfigurationComponent', () => {
|
||||
|
||||
|
Reference in New Issue
Block a user