fixed and tests for equals method

This commit is contained in:
lotte
2019-07-17 16:28:17 +02:00
parent 2bfe6ceebb
commit d16ee88641
23 changed files with 128 additions and 58 deletions

View File

@@ -5,7 +5,7 @@ import { ListableObject } from '../shared/object-collection/shared/listable-obje
/** /**
* Represents a search result object of a certain (<T>) DSpaceObject * 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 * The DSpaceObject that was found
*/ */

View File

@@ -1,7 +1,6 @@
import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer'; 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 { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { EPerson } from './eperson.model'; import { EPerson } from './eperson.model';
import { mapsTo, relationship } from '../../cache/builders/build-decorators'; import { mapsTo, relationship } from '../../cache/builders/build-decorators';
@@ -9,7 +8,7 @@ import { ResourceType } from '../../shared/resource-type';
@mapsTo(EPerson) @mapsTo(EPerson)
@inheritSerialization(NormalizedDSpaceObject) @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 * A string representing the unique handle of this EPerson

View File

@@ -1,7 +1,6 @@
import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer'; 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 { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { mapsTo, relationship } from '../../cache/builders/build-decorators'; import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { Group } from './group.model'; import { Group } from './group.model';
@@ -9,7 +8,7 @@ import { ResourceType } from '../../shared/resource-type';
@mapsTo(Group) @mapsTo(Group)
@inheritSerialization(NormalizedDSpaceObject) @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 * List of Groups that this Group belong to
@@ -36,3 +35,4 @@ export class NormalizedGroup extends NormalizedDSpaceObject<Group> implements Ca
@autoserialize @autoserialize
public permanent: boolean; public permanent: boolean;
} }

View File

@@ -3,7 +3,7 @@ import { autoserialize } from 'cerialize';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
export class MetadataField implements ListableObject { export class MetadataField extends ListableObject {
@autoserialize @autoserialize
id: number; id: number;

View File

@@ -1,16 +1,13 @@
import { autoserialize } from 'cerialize'; import { autoserialize } from 'cerialize';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { CacheableObject } from '../cache/object-cache.reducer';
export class MetadataSchema implements ListableObject { export class MetadataSchema extends ListableObject {
@autoserialize
id: number; id: number;
@autoserialize
self: string; self: string;
@autoserialize
prefix: string; prefix: string;
@autoserialize
namespace: string; namespace: string;
} }

View File

@@ -1,14 +1,13 @@
import { autoserialize } from 'cerialize'; import { autoserialize } from 'cerialize';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { mapsTo } from '../cache/builders/build-decorators'; import { mapsTo } from '../cache/builders/build-decorators';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { MetadataSchema } from './metadataschema.model'; import { MetadataSchema } from './metadataschema.model';
/** /**
* Normalized class for a DSpace MetadataSchema * Normalized class for a DSpace MetadataSchema
*/ */
@mapsTo(MetadataSchema) @mapsTo(MetadataSchema)
export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> implements ListableObject { export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> {
/** /**
* The unique identifier for this schema * The unique identifier for this schema
*/ */
@@ -32,4 +31,5 @@ export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> i
*/ */
@autoserialize @autoserialize
namespace: string; namespace: string;
} }

View File

@@ -400,7 +400,7 @@ export class RegistryService {
distinctUntilChanged() 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( const request$ = endpoint$.pipe(
take(1), take(1),

View File

@@ -1,8 +1,8 @@
import { autoserialize, autoserializeAs } from 'cerialize'; import { autoserialize, autoserializeAs } from 'cerialize';
import { Equatable } from '../utilities/equatable'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { hasValue } from '../../shared/empty.util'; import { excludeFromEquals } from '../utilities/equals.decorators';
export class BrowseEntry implements Equatable<BrowseEntry> { export class BrowseEntry extends ListableObject {
@autoserialize @autoserialize
type: string; type: string;
@@ -19,11 +19,4 @@ export class BrowseEntry implements Equatable<BrowseEntry> {
@autoserialize @autoserialize
count: number; count: number;
equals(other: BrowseEntry): boolean {
if (hasValue(other)) {
return false;
}
return false;
}
} }

View File

@@ -12,7 +12,7 @@ import { hasNoValue } from '../../shared/empty.util';
/** /**
* An abstract model class for a DSpaceObject. * An abstract model class for a DSpaceObject.
*/ */
export class DSpaceObject implements CacheableObject, ListableObject { export class DSpaceObject extends ListableObject implements CacheableObject {
private _name: string; private _name: string;

View File

@@ -1,10 +1,10 @@
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { SearchSidebarService } from './search-sidebar.service'; import { SearchSidebarService } from './search-sidebar.service';
import { AppState } from '../../app.reducer';
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from '../../../shared/search/search-sidebar/search-sidebar.actions'; 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', () => { describe('SearchSidebarService', () => {
let service: SearchSidebarService; let service: SearchSidebarService;

View File

@@ -1,7 +1,6 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { CacheableObject } from '../../cache/object-cache.reducer'; 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 { DSpaceObject } from '../../shared/dspace-object.model';
import { EPerson } from '../../eperson/models/eperson.model'; import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
@@ -18,7 +17,7 @@ export interface SubmissionObjectError {
/** /**
* An abstract model class for a SubmissionObject. * 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 * The workspaceitem/workflowitem identifier

View File

@@ -2,14 +2,13 @@ import { Observable } from 'rxjs';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { DSpaceObject } from '../../shared/dspace-object.model'; import { DSpaceObject } from '../../shared/dspace-object.model';
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { Workflowitem } from '../../submission/models/workflowitem.model'; import { Workflowitem } from '../../submission/models/workflowitem.model';
/** /**
* An abstract model class for a TaskObject. * 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 * The task identifier

View File

@@ -34,6 +34,6 @@ export function fieldsForEquals(...fields: string[]): any {
export function getFieldsForEquals(constructor: Function, field: string) { 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); return fieldMap.get(field);
} }

View 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);
});
});

View File

@@ -12,11 +12,11 @@ function equalsByFields(object1, object2, fieldList): boolean {
if (hasNoValue(object1[key]) || hasNoValue(object2[key])) { if (hasNoValue(object1[key]) || hasNoValue(object2[key])) {
return true; return true;
} }
const mapping = getFieldsForEquals(this.constructor, key); const mapping = getFieldsForEquals(object1.constructor, key);
if (hasValue(mapping)) { if (hasValue(mapping)) {
return !equalsByFields(object1[key], object2[key], 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].equals(object2[key]);
} }
return object1[key] !== object2[key]; return object1[key] !== object2[key];
@@ -26,6 +26,12 @@ function equalsByFields(object1, object2, fieldList): boolean {
export abstract class EquatableObject<T> { export abstract class EquatableObject<T> {
equals(other: T): boolean { equals(other: T): boolean {
if (hasNoValue(other)) {
return false;
}
if (this as any === other) {
return true;
}
const excludedKeys = getExcludedFromEqualsFor(this.constructor); const excludedKeys = getExcludedFromEqualsFor(this.constructor);
const keys = Object.keys(this).filter((key) => excludedKeys.findIndex((excludedKey) => key === excludedKey) < 0); const keys = Object.keys(this).filter((key) => excludedKeys.findIndex((excludedKey) => key === excludedKey) < 0);
return equalsByFields(this, other, keys); return equalsByFields(this, other, keys);

View File

@@ -8,15 +8,14 @@ import { PaginatedSearchOptions } from '../../../../../search/paginated-search-o
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue } from '../../../../../empty.util';
import { getSucceededRemoteData } from '../../../../../../core/shared/operators'; import { concat, map, multicast, take, takeWhile } from 'rxjs/operators';
import { concat, map, multicast, take, takeWhile, tap } from 'rxjs/operators';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.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 { 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='; const RELATION_TYPE_FILTER_PREFIX = 'f.entityType=';

View File

@@ -0,0 +1,3 @@
import { EquatableObject } from '../../../core/utilities/equatable';
export class ListableObject extends EquatableObject<ListableObject>{}

View File

@@ -26,10 +26,12 @@ export class TypedItemSearchResultListElementComponent extends SearchResultListE
this.object = obj as ItemSearchResult; this.object = obj as ItemSearchResult;
this.dso = this.object.indexableObject; this.dso = this.object.indexableObject;
} else { } else {
this.object = { this.object = Object.assign(
indexableObject: obj as Item, new ItemSearchResult(),
hitHighlights: new MetadataMap() {
}; indexableObject: obj as Item,
hitHighlights: new MetadataMap()
});
this.dso = obj as Item; this.dso = obj as Item;
} }
this.item = this.dso; this.item = this.dso;

View File

@@ -80,13 +80,13 @@ function selectSingle(state: SelectableListState, action: SelectableListSelectSi
} }
function deselect(state: SelectableListState, action: SelectableListDeselectAction) { 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 }); return Object.assign({}, state, { selection: newSelection });
} }
function deselectSingle(state: SelectableListState, action: SelectableListDeselectSingleAction) { function deselectSingle(state: SelectableListState, action: SelectableListDeselectSingleAction) {
const newSelection = state.selection.filter((selected) => { const newSelection = state.selection.filter((selected) => {
return selected.uuid !== action.payload.uuid return !selected.equals(action.payload);
}); });
return Object.assign({}, state, { selection: newSelection }); return Object.assign({}, state, { selection: newSelection });
} }
@@ -101,5 +101,5 @@ function clearSelection(id: string) {
function isObjectInSelection(selection: ListableObject[], object: ListableObject) { function isObjectInSelection(selection: ListableObject[], object: ListableObject) {
return selection.findIndex((selected) => selected.uuid === object.uuid) >= 0 return selection.findIndex((selected) => selected.equals(object)) >= 0
} }

View File

@@ -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 * 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 * The UUID of the DSpaceObject that was found
*/ */

View File

@@ -2,15 +2,15 @@ import { SearchLabelsComponent } from './search-labels.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { SearchService } from '../search-service/search.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe'; import { ObjectKeysPipe } from '../../utils/object-keys-pipe';
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; import { SearchServiceStub } from '../../testing/search-service-stub';
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-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', () => { describe('SearchLabelsComponent', () => {
let comp: SearchLabelsComponent; let comp: SearchLabelsComponent;

View File

@@ -5,7 +5,7 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode
/** /**
* Represents a search result object of a certain (<T>) DSpaceObject * 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 * The DSpaceObject that was found
*/ */

View File

@@ -6,13 +6,16 @@ import { of as observableOf } from 'rxjs';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { SearchSwitchConfigurationComponent } from './search-switch-configuration.component'; 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 { NavigationExtras, Router } from '@angular/router';
import { RouterStub } from '../../shared/testing/router-stub'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service-stub';
import { MyDSpaceConfigurationValueType } from '../../+my-dspace-page/my-dspace-configuration-value-type'; import { RouterStub } from '../../testing/router-stub';
import { SearchService } from '../search-service/search.service'; 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', () => { describe('SearchSwitchConfigurationComponent', () => {