93747: Test cases

This commit is contained in:
Kristof De Langhe
2023-01-03 16:16:11 +01:00
parent 9d3769103d
commit 533d833225
5 changed files with 378 additions and 33 deletions

View File

@@ -11,10 +11,16 @@ import { RelationshipService } from './relationship.service';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { RequestEntry } from './request.reducer'; import { RequestEntry } from './request.reducer';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock'; import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock';
import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { getMockRequestService } from '../../shared/mocks/request.service.mock';
import { createPaginatedList } from '../../shared/testing/utils.test'; import { createPaginatedList } from '../../shared/testing/utils.test';
import { MetadataValue } from '../shared/metadata.models';
import { MetadataRepresentationType } from '../shared/metadata-representation/metadata-representation.model';
describe('RelationshipService', () => { describe('RelationshipService', () => {
let service: RelationshipService; let service: RelationshipService;
@@ -228,4 +234,152 @@ describe('RelationshipService', () => {
}); });
}); });
}); });
describe('resolveMetadataRepresentation', () => {
const parentItem: Item = Object.assign(new Item(), {
id: 'parent-item',
metadata: {
'dc.contributor.author': [
Object.assign(new MetadataValue(), {
language: null,
value: 'Related Author with authority',
authority: 'virtual::related-author',
place: 2
}),
Object.assign(new MetadataValue(), {
language: null,
value: 'Author without authority',
place: 1
}),
],
'dc.creator': [
Object.assign(new MetadataValue(), {
language: null,
value: 'Related Creator with authority',
authority: 'virtual::related-creator',
place: 3,
}),
Object.assign(new MetadataValue(), {
language: null,
value: 'Related Creator with authority - unauthorized',
authority: 'virtual::related-creator-unauthorized',
place: 4,
}),
],
'dc.title': [
Object.assign(new MetadataValue(), {
language: null,
value: 'Parent Item'
}),
]
}
});
const relatedAuthor: Item = Object.assign(new Item(), {
id: 'related-author',
metadata: {
'dc.title': [
Object.assign(new MetadataValue(), {
language: null,
value: 'Related Author'
}),
]
}
});
const relatedCreator: Item = Object.assign(new Item(), {
id: 'related-creator',
metadata: {
'dc.title': [
Object.assign(new MetadataValue(), {
language: null,
value: 'Related Creator'
}),
],
'dspace.entity.type': 'Person',
}
});
const authorRelation: Relationship = Object.assign(new Relationship(), {
leftItem: createSuccessfulRemoteDataObject$(parentItem),
rightItem: createSuccessfulRemoteDataObject$(relatedAuthor)
});
const creatorRelation: Relationship = Object.assign(new Relationship(), {
leftItem: createSuccessfulRemoteDataObject$(parentItem),
rightItem: createSuccessfulRemoteDataObject$(relatedCreator),
});
const creatorRelationUnauthorized: Relationship = Object.assign(new Relationship(), {
leftItem: createSuccessfulRemoteDataObject$(parentItem),
rightItem: createFailedRemoteDataObject$('Unauthorized', 401),
});
let metadatum: MetadataValue;
beforeEach(() => {
service.findById = (id: string) => {
if (id === 'related-author') {
return createSuccessfulRemoteDataObject$(authorRelation);
}
if (id === 'related-creator') {
return createSuccessfulRemoteDataObject$(creatorRelation);
}
if (id === 'related-creator-unauthorized') {
return createSuccessfulRemoteDataObject$(creatorRelationUnauthorized);
}
};
});
describe('when the metadata isn\'t virtual', () => {
beforeEach(() => {
metadatum = parentItem.metadata['dc.contributor.author'][1];
});
it('should return a plain text MetadatumRepresentation', (done) => {
service.resolveMetadataRepresentation(metadatum, parentItem, 'Person').subscribe((result) => {
expect(result.representationType).toEqual(MetadataRepresentationType.PlainText);
done();
});
});
});
describe('when the metadata is a virtual author', () => {
beforeEach(() => {
metadatum = parentItem.metadata['dc.contributor.author'][0];
});
it('should return a ItemMetadataRepresentation with the correct value', (done) => {
service.resolveMetadataRepresentation(metadatum, parentItem, 'Person').subscribe((result) => {
expect(result.representationType).toEqual(MetadataRepresentationType.Item);
expect(result.getValue()).toEqual(metadatum.value);
expect((result as any).id).toEqual(relatedAuthor.id);
done();
});
});
});
describe('when the metadata is a virtual creator', () => {
beforeEach(() => {
metadatum = parentItem.metadata['dc.creator'][0];
});
it('should return a ItemMetadataRepresentation with the correct value', (done) => {
service.resolveMetadataRepresentation(metadatum, parentItem, 'Person').subscribe((result) => {
expect(result.representationType).toEqual(MetadataRepresentationType.Item);
expect(result.getValue()).toEqual(metadatum.value);
expect((result as any).id).toEqual(relatedCreator.id);
done();
});
});
});
describe('when the metadata refers to a relationship leading to an error response', () => {
beforeEach(() => {
metadatum = parentItem.metadata['dc.creator'][1];
});
it('should return an authority controlled MetadatumRepresentation', (done) => {
service.resolveMetadataRepresentation(metadatum, parentItem, 'Person').subscribe((result) => {
expect(result.representationType).toEqual(MetadataRepresentationType.AuthorityControlled);
done();
});
});
});
});
}); });

View File

@@ -544,14 +544,20 @@ export class RelationshipService extends DataService<Relationship> {
filter(([leftItem, rightItem]) => leftItem.hasCompleted && rightItem.hasCompleted), filter(([leftItem, rightItem]) => leftItem.hasCompleted && rightItem.hasCompleted),
map(([leftItem, rightItem]) => { map(([leftItem, rightItem]) => {
if (!leftItem.hasSucceeded || !rightItem.hasSucceeded) { if (!leftItem.hasSucceeded || !rightItem.hasSucceeded) {
return observableOf(Object.assign(new MetadatumRepresentation(itemType), metadatum)); return null;
} else if (rightItem.hasSucceeded && leftItem.payload.id === parentItem.id) { } else if (rightItem.hasSucceeded && leftItem.payload.id === parentItem.id) {
return rightItem.payload; return rightItem.payload;
} else if (rightItem.payload.id === parentItem.id) { } else if (rightItem.payload.id === parentItem.id) {
return leftItem.payload; return leftItem.payload;
} }
}), }),
map((item: Item) => Object.assign(new ItemMetadataRepresentation(metadatum), item)) map((item: Item) => {
if (hasValue(item)) {
return Object.assign(new ItemMetadataRepresentation(metadatum), item);
} else {
return Object.assign(new MetadatumRepresentation(itemType), metadatum);
}
})
) )
)); ));
} else { } else {

View File

@@ -1,18 +1,18 @@
<div class="item-metadata" *ngIf="form"> <div class="item-metadata" *ngIf="form">
<div class="button-row top d-flex my-2 space-children-mr ml-gap"> <div class="button-row top d-flex my-2 space-children-mr ml-gap">
<button class="mr-auto btn btn-success" [disabled]="form.newValue || (saving$ | async)" <button class="mr-auto btn btn-success" id="dso-add-btn" [disabled]="form.newValue || (saving$ | async)"
(click)="add()"><i class="fas fa-plus"></i> (click)="add()"><i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.add-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.add-button' | translate }}</span>
</button> </button>
<button class="btn btn-warning ml-1" *ngIf="isReinstatable" [disabled]="(saving$ | async)" <button class="btn btn-warning ml-1" id="dso-reinstate-btn" *ngIf="isReinstatable" [disabled]="(saving$ | async)"
(click)="reinstate()"><i class="fas fa-undo-alt"></i> (click)="reinstate()"><i class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.reinstate-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.reinstate-button' | translate }}</span>
</button> </button>
<button class="btn btn-primary ml-1" [disabled]="!hasChanges || (saving$ | async)" <button class="btn btn-primary ml-1" id="dso-save-btn" [disabled]="!hasChanges || (saving$ | async)"
(click)="submit()"><i class="fas fa-save"></i> (click)="submit()"><i class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.save-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.save-button' | translate }}</span>
</button> </button>
<button class="btn btn-danger ml-1" *ngIf="!isReinstatable" <button class="btn btn-danger ml-1" id="dso-discard-btn" *ngIf="!isReinstatable"
[disabled]="!hasChanges || (saving$ | async)" [disabled]="!hasChanges || (saving$ | async)"
(click)="discard()"><i class="fas fa-times"></i> (click)="discard()"><i class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.discard-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.discard-button' | translate }}</span>

View File

@@ -0,0 +1,189 @@
import { DsoEditMetadataComponent } from './dso-edit-metadata.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { VarDirective } from '../../shared/utils/var.directive';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { Item } from '../../core/shared/item.model';
import { ItemDataService } from '../../core/data/item-data.service';
import { MetadataValue } from '../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { By } from '@angular/platform-browser';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { ArrayMoveChangeAnalyzer } from '../../core/data/array-move-change-analyzer.service';
import { ITEM } from '../../core/shared/item.resource-type';
const ADD_BTN = 'add';
const REINSTATE_BTN = 'reinstate';
const SAVE_BTN = 'save';
const DISCARD_BTN = 'discard';
describe('DsoEditMetadataComponent', () => {
let component: DsoEditMetadataComponent;
let fixture: ComponentFixture<DsoEditMetadataComponent>;
let notificationsService: NotificationsService;
let dso: DSpaceObject;
let updatedDso: DSpaceObject;
let dataService: ItemDataService;
beforeEach(waitForAsync(() => {
dso = Object.assign(new Item(), {
type: ITEM,
metadata: {
'dc.title': [
Object.assign(new MetadataValue(), {
value: 'Test Title',
language: 'en',
place: 0,
}),
],
'dc.subject': [
Object.assign(new MetadataValue(), {
value: 'Subject One',
language: 'en',
place: 0,
}),
Object.assign(new MetadataValue(), {
value: 'Subject Two',
language: 'en',
place: 1,
}),
Object.assign(new MetadataValue(), {
value: 'Subject Three',
language: 'en',
place: 2,
}),
],
},
});
updatedDso = Object.assign(new Item(), dso);
updatedDso.metadata['dc.title'][0].value = 'Updated Title';
dataService = jasmine.createSpyObj('itemDataService', {
patch: createSuccessfulRemoteDataObject$(updatedDso),
});
notificationsService = jasmine.createSpyObj('notificationsService', ['error', 'success']);
TestBed.configureTestingModule({
declarations: [DsoEditMetadataComponent, VarDirective],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
providers: [
{ provide: ItemDataService, useValue: dataService },
{ provide: NotificationsService, useValue: notificationsService },
ArrayMoveChangeAnalyzer,
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsoEditMetadataComponent);
component = fixture.componentInstance;
component.dso = dso;
fixture.detectChanges();
});
describe('when no changes have been made', () => {
assertButton(ADD_BTN, true, false);
assertButton(REINSTATE_BTN, false);
assertButton(SAVE_BTN, true, true);
assertButton(DISCARD_BTN, true, true);
});
describe('when the form contains changes', () => {
beforeEach(() => {
component.form.fields['dc.title'][0].newValue.value = 'Updated Title Once';
component.form.fields['dc.title'][0].confirmChanges();
component.form.resetReinstatable();
component.onValueSaved();
fixture.detectChanges();
});
assertButton(SAVE_BTN, true, false);
assertButton(DISCARD_BTN, true, false);
describe('and they were discarded', () => {
beforeEach(() => {
component.discard();
fixture.detectChanges();
});
assertButton(REINSTATE_BTN, true, false);
assertButton(SAVE_BTN, true, true);
assertButton(DISCARD_BTN, false);
describe('and a new change is made', () => {
beforeEach(() => {
component.form.fields['dc.title'][0].newValue.value = 'Updated Title Twice';
component.form.fields['dc.title'][0].confirmChanges();
component.form.resetReinstatable();
component.onValueSaved();
fixture.detectChanges();
});
assertButton(REINSTATE_BTN, false);
assertButton(SAVE_BTN, true, false);
assertButton(DISCARD_BTN, true, false);
});
});
});
describe('when a new value is present', () => {
beforeEach(() => {
component.add();
fixture.detectChanges();
});
assertButton(ADD_BTN, true, true);
it('should display a row with a field selector and metadata value', () => {
expect(fixture.debugElement.query(By.css('ds-metadata-field-selector'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('ds-dso-edit-metadata-value'))).toBeTruthy();
});
describe('and gets assigned to a metadata field', () => {
beforeEach(() => {
component.form.newValue.newValue.value = 'New Subject';
component.form.setMetadataField('dc.subject');
component.form.resetReinstatable();
component.onValueSaved();
fixture.detectChanges();
});
assertButton(ADD_BTN, true, false);
it('should not display the separate row with field selector and metadata value anymore', () => {
expect(fixture.debugElement.query(By.css('ds-metadata-field-selector'))).toBeNull();
expect(fixture.debugElement.query(By.css('ds-dso-edit-metadata-value'))).toBeNull();
});
});
});
function assertButton(name: string, exists: boolean, disabled: boolean = false): void {
describe(`${name} button`, () => {
let btn: DebugElement;
beforeEach(() => {
btn = fixture.debugElement.query(By.css(`#dso-${name}-btn`));
});
if (exists) {
it('should exist', () => {
expect(btn).toBeTruthy();
});
it(`should${disabled ? ' ' : ' not '}be disabled`, () => {
expect(btn.nativeElement.disabled).toBe(disabled);
});
} else {
it('should not exist', () => {
expect(btn).toBeNull();
});
}
});
}
});

View File

@@ -9,6 +9,10 @@ import { createSuccessfulRemoteDataObject$, createFailedRemoteDataObject$ } from
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { MetadataValue } from '../../../core/shared/metadata.models';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
const itemType = 'Person'; const itemType = 'Person';
const metadataFields = ['dc.contributor.author', 'dc.creator']; const metadataFields = ['dc.contributor.author', 'dc.creator'];
@@ -73,39 +77,31 @@ const relatedCreator: Item = Object.assign(new Item(), {
'dspace.entity.type': 'Person', 'dspace.entity.type': 'Person',
} }
}); });
const authorRelation: Relationship = Object.assign(new Relationship(), {
leftItem: createSuccessfulRemoteDataObject$(parentItem),
rightItem: createSuccessfulRemoteDataObject$(relatedAuthor)
});
const creatorRelation: Relationship = Object.assign(new Relationship(), {
leftItem: createSuccessfulRemoteDataObject$(parentItem),
rightItem: createSuccessfulRemoteDataObject$(relatedCreator),
});
const creatorRelationUnauthorized: Relationship = Object.assign(new Relationship(), {
leftItem: createSuccessfulRemoteDataObject$(parentItem),
rightItem: createFailedRemoteDataObject$('Unauthorized', 401),
});
let relationshipService;
describe('MetadataRepresentationListComponent', () => { describe('MetadataRepresentationListComponent', () => {
let comp: MetadataRepresentationListComponent; let comp: MetadataRepresentationListComponent;
let fixture: ComponentFixture<MetadataRepresentationListComponent>; let fixture: ComponentFixture<MetadataRepresentationListComponent>;
let relationshipService;
beforeEach(waitForAsync(() => {
relationshipService = { relationshipService = {
findById: (id: string) => { resolveMetadataRepresentation: (metadatum: MetadataValue, parent: DSpaceObject, type: string) => {
if (id === 'related-author') { if (metadatum.value === 'Related Author with authority') {
return createSuccessfulRemoteDataObject$(authorRelation); return observableOf(Object.assign(new ItemMetadataRepresentation(metadatum), relatedAuthor));
} }
if (id === 'related-creator') { if (metadatum.value === 'Author without authority') {
return createSuccessfulRemoteDataObject$(creatorRelation); return observableOf(Object.assign(new MetadatumRepresentation(type), metadatum));
} }
if (id === 'related-creator-unauthorized') { if (metadatum.value === 'Related Creator with authority') {
return createSuccessfulRemoteDataObject$(creatorRelationUnauthorized); return observableOf(Object.assign(new ItemMetadataRepresentation(metadatum), relatedCreator));
}
if (metadatum.value === 'Related Creator with authority - unauthorized') {
return observableOf(Object.assign(new MetadatumRepresentation(type), metadatum));
} }
}, },
}; };
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()], imports: [TranslateModule.forRoot()],
declarations: [MetadataRepresentationListComponent, VarDirective], declarations: [MetadataRepresentationListComponent, VarDirective],