This commit is contained in:
lotte
2019-11-26 16:43:46 +01:00
parent 835ce735cb
commit 8522c35186
28 changed files with 892 additions and 147 deletions

View File

@@ -10,20 +10,37 @@ import { RelationshipTypeService } from './relationship-type.service';
import { of as observableOf } from 'rxjs';
import { ItemType } from '../shared/item-relationships/item-type.model';
fdescribe('RelationshipTypeService', () => {
describe('RelationshipTypeService', () => {
let service: RelationshipTypeService;
let requestService: RequestService;
let requestService : RequestService;
let restEndpointURL;
let halService: any;
let publicationTypeString;
let personTypeString;
let orgUnitTypeString;
let publicationType;
let personType;
let orgUnitType;
const restEndpointURL = 'https://rest.api/relationshiptypes';
const halService: any = new HALEndpointServiceStub(restEndpointURL);
const publicationTypeString = 'Publication';
const personTypeString = 'Person';
const orgUnitTypeString = 'OrgUnit';
const publicationType = Object.assign(new ItemType(), {label: publicationTypeString});
const personType = Object.assign(new ItemType(), {label: personTypeString});
const orgUnitType = Object.assign(new ItemType(), {label: orgUnitTypeString});
let relationshipType1;
const relationshipType1 = Object.assign(new RelationshipType(), {
let relationshipType2;
let buildList;
let rdbService;
function init() {
restEndpointURL = 'https://rest.api/relationshiptypes';
halService = new HALEndpointServiceStub(restEndpointURL);
publicationTypeString = 'Publication';
personTypeString = 'Person';
orgUnitTypeString = 'OrgUnit';
publicationType = Object.assign(new ItemType(), {label: publicationTypeString});
personType = Object.assign(new ItemType(), {label: personTypeString});
orgUnitType = Object.assign(new ItemType(), {label: orgUnitTypeString});
relationshipType1 = Object.assign(new RelationshipType(), {
id: '1',
uuid: '1',
leftwardType: 'isAuthorOfPublication',
@@ -33,7 +50,7 @@ fdescribe('RelationshipTypeService', () => {
});
const relationshipType2 = Object.assign(new RelationshipType(), {
relationshipType2 = Object.assign(new RelationshipType(), {
id: '2',
uuid: '2',
leftwardType: 'isOrgUnitOfPublication',
@@ -42,9 +59,10 @@ fdescribe('RelationshipTypeService', () => {
rightType: createSuccessfulRemoteDataObject$(orgUnitType)
});
const buildList = createSuccessfulRemoteDataObject(new PaginatedList(new PageInfo(), [relationshipType1, relationshipType2]));
const rdbService = getMockRemoteDataBuildService(undefined, observableOf(buildList));
buildList = createSuccessfulRemoteDataObject(new PaginatedList(new PageInfo(), [relationshipType1, relationshipType2]));
rdbService = getMockRemoteDataBuildService(undefined, observableOf(buildList));
}
function initTestService() {
return new RelationshipTypeService(
requestService,
@@ -54,6 +72,7 @@ fdescribe('RelationshipTypeService', () => {
}
beforeEach(() => {
init();
requestService = getMockRequestService();
service = initTestService();
});

View File

@@ -137,7 +137,7 @@ export class RelationshipService extends DataService<Relationship> {
this.requestService.hasByHrefObservable(item.self)
).pipe(
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
tap(t => console.log(t)),
take(1),
switchMap(() => this.itemService.findByHref(item.self).pipe(take(1)))
).subscribe();
}
@@ -330,7 +330,6 @@ export class RelationshipService extends DataService<Relationship> {
}),
// skipWhile((relationshipRD: RemoteData<Relationship>) => !relationshipRD.isSuccessful)
tap((relationshipRD: RemoteData<Relationship>) => {
console.log(relationshipRD.payload);
if (relationshipRD.hasSucceeded) {
this.removeRelationshipItemsFromCache(item1);
this.removeRelationshipItemsFromCache(item2);

View File

@@ -22,7 +22,9 @@ import { PersonItemMetadataListElementComponent } from './metadata-representatio
import { OrgUnitItemMetadataListElementComponent } from './metadata-representations/org-unit/org-unit-item-metadata-list-element.component';
import { PersonSearchResultListSubmissionElementComponent } from './submission/item-list-elements/person/person-search-result-list-submission-element.component';
import { PersonInputSuggestionsComponent } from './submission/item-list-elements/person/person-suggestions/person-input-suggestions.component';
import { NameVariantModalComponent } from './submission/item-list-elements/person/name-variant-modal/name-variant-modal.component';
import { NameVariantModalComponent } from './submission/name-variant-modal/name-variant-modal.component';
import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component';
import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component';
const ENTRY_COMPONENTS = [
OrgUnitComponent,
@@ -44,7 +46,9 @@ const ENTRY_COMPONENTS = [
ProjectSearchResultGridElementComponent,
PersonSearchResultListSubmissionElementComponent,
PersonInputSuggestionsComponent,
NameVariantModalComponent
NameVariantModalComponent,
OrgUnitSearchResultListSubmissionElementComponent,
OrgUnitInputSuggestionsComponent
];
@NgModule({

View File

@@ -0,0 +1,20 @@
<div class="d-flex">
<div class="person-thumbnail pr-2">
<!-- <ds-thumbnail [thumbnail]="dso.getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>-->
</div>
<div class="flex-grow-1">
<ds-org-unit-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)"
(submitSuggestion)="selectCustom($event)"></ds-org-unit-input-suggestions>
<span class="text-muted">
<span *ngIf="dso.allMetadata('organization.address.addressLocality').length > 0"
class="item-list-job-title">
<span [innerHTML]="firstMetadataValue(['organization.address.addressLocality'])"><span [innerHTML]="firstMetadataValue(['organization.address.addressLocality'])"></span></span>
<span *ngIf="dso.allMetadata('organization.address.addressCountry').length > 0">, </span>
</span>
<span *ngIf="dso.allMetadata('organization.address.addressCountry').length > 0"
class="item-list-job-title">
<span [innerHTML]="firstMetadataValue(['organization.address.addressCountry'])"><span [innerHTML]="firstMetadataValue(['organization.address.addressCountry'])"></span></span>
</span>
</span>
</div>
</div>

View File

@@ -0,0 +1,7 @@
@import '../../../../../../styles/variables';
$submission-relationship-thumbnail-width: 80px;
.person-thumbnail {
width: $submission-relationship-thumbnail-width;
}

View File

@@ -0,0 +1,125 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { of as observableOf } from 'rxjs';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { OrgUnitSearchResultListSubmissionElementComponent } from './org-unit-search-result-list-submission-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { RelationshipService } from '../../../../../core/data/relationship.service';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
import { Store } from '@ngrx/store';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
let personListElementComponent: OrgUnitSearchResultListSubmissionElementComponent;
let fixture: ComponentFixture<OrgUnitSearchResultListSubmissionElementComponent>;
let mockItemWithMetadata: ItemSearchResult;
let mockItemWithoutMetadata: ItemSearchResult;
let nameVariant;
let mockRelationshipService;
function init() {
mockItemWithMetadata = Object.assign(
new ItemSearchResult(),
{
indexableObject: Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(undefined, [])),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'person.jobTitle': [
{
language: 'en_US',
value: 'Developer'
}
]
}
})
});
mockItemWithoutMetadata = Object.assign(
new ItemSearchResult(),
{
indexableObject: Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(undefined, [])),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
})
});
nameVariant = "Doe J.";
mockRelationshipService = {
getNameVariant: () => observableOf(nameVariant)
};
}
describe('PersonSearchResultListElementSubmissionComponent', () => {
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
declarations: [OrgUnitSearchResultListSubmissionElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: {} },
{ provide: RelationshipService, useValue: mockRelationshipService },
{ provide: NotificationsService, useValue: {} },
{ provide: TranslateService, useValue: {} },
{ provide: NgbModal, useValue: {} },
{ provide: ItemDataService, useValue: {} },
{ provide: SelectableListService, useValue: {} },
{ provide: Store, useValue: {}}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(OrgUnitSearchResultListSubmissionElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(OrgUnitSearchResultListSubmissionElementComponent);
personListElementComponent = fixture.componentInstance;
}));
describe('When the item has a job title', () => {
beforeEach(() => {
personListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should show the job title span', () => {
const jobTitleField = fixture.debugElement.query(By.css('span.item-list-job-title'));
expect(jobTitleField).not.toBeNull();
});
});
describe('When the item has no job title', () => {
beforeEach(() => {
personListElementComponent.object = mockItemWithoutMetadata;
fixture.detectChanges();
});
it('should not show the job title span', () => {
const jobTitleField = fixture.debugElement.query(By.css('span.item-list-job-title'));
expect(jobTitleField).toBeNull();
});
});
});

View File

@@ -0,0 +1,98 @@
import { Component, OnInit } from '@angular/core';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Item } from '../../../../../core/shared/item.model';
import { Context } from '../../../../../core/shared/context.model';
import { RelationshipService } from '../../../../../core/data/relationship.service';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { take } from 'rxjs/operators';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { MetadataValue } from '../../../../../core/shared/metadata.models';
import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component';
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.Workspace)
@Component({
selector: 'ds-person-search-result-list-submission-element',
styleUrls: ['./org-unit-search-result-list-submission-element.component.scss'],
templateUrl: './org-unit-search-result-list-submission-element.component.html'
})
/**
* The component for displaying a list element for an item search result of the type Person
*/
export class OrgUnitSearchResultListSubmissionElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> implements OnInit {
allSuggestions: string[];
selectedName: string;
alternativeField = 'dc.title.alternative';
constructor(protected truncatableService: TruncatableService,
private relationshipService: RelationshipService,
private notificationsService: NotificationsService,
private translateService: TranslateService,
private modalService: NgbModal,
private itemDataService: ItemDataService,
private selectableListService: SelectableListService) {
super(truncatableService);
}
ngOnInit() {
super.ngOnInit();
const defaultValue = this.firstMetadataValue('organization.legalName');
const alternatives = this.allMetadataValues(this.alternativeField);
this.allSuggestions = [defaultValue, ...alternatives];
this.relationshipService.getNameVariant(this.listID, this.dso.uuid)
.pipe(take(1))
.subscribe((nameVariant: string) => {
this.selectedName = nameVariant || defaultValue;
}
);
}
select(value) {
this.selectableListService.isObjectSelected(this.listID, this.object)
.pipe(take(1))
.subscribe((selected) => {
if (!selected) {
this.selectableListService.selectSingle(this.listID, this.object);
}
});
this.relationshipService.setNameVariant(this.listID, this.dso.uuid, value);
}
selectCustom(value) {
if (!this.allSuggestions.includes(value)) {
this.openModal(value)
.then(() => {
const newName: MetadataValue = new MetadataValue();
newName.value = value;
const existingNames: MetadataValue[] = this.dso.metadata[this.alternativeField] || [];
const alternativeNames = { [this.alternativeField]: [...existingNames, newName] };
const updatedItem =
Object.assign({}, this.dso, {
metadata: {
...this.dso.metadata,
...alternativeNames
},
});
this.itemDataService.update(updatedItem).pipe(take(1)).subscribe();
})
}
this.select(value);
}
openModal(value): Promise<any> {
const modalRef = this.modalService.open(NameVariantModalComponent, { centered: true });
const modalComp = modalRef.componentInstance;
modalComp.value = value;
return modalRef.result;
}
}

View File

@@ -0,0 +1,24 @@
<form #form="ngForm" (ngSubmit)="onSubmit(value)"
[action]="action" (keydown)="onKeydown($event)"
(keydown.arrowdown)="shiftFocusDown($event)"
(keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()"
(dsClickOutside)="close();">
<input #inputField type="text" [(ngModel)]="value" [name]="name"
class="form-control suggestion_input"
(focus)="open()"
(click)="open()"
[ngClass]="{'is-invalid': !valid}"
[dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions">
<a href="#" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
<span [innerHTML]="suggestionOption"></span>
</a>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,18 @@
form {
z-index: 1;
&:before {
position: absolute;
font-weight: 900;
font-family: "Font Awesome 5 Free";
content: "\f0d7";
top: 7px;
right: 0;
height: 20px;
width: 20px;
z-index: -1;
}
input.suggestion_input {
background: transparent;
}
}

View File

@@ -0,0 +1,48 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { InputSuggestionsComponent } from '../../../../../../shared/input-suggestions/input-suggestions.component';
@Component({
selector: 'ds-org-unit-input-suggestions',
styleUrls: ['./org-unit-input-suggestions.component.scss', './../../../../../../shared/input-suggestions/input-suggestions.component.scss'],
templateUrl: './org-unit-input-suggestions.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
// Usage of forwardRef necessary https://github.com/angular/angular.io/issues/1151
// tslint:disable-next-line:no-forward-ref
useExisting: forwardRef(() => OrgUnitInputSuggestionsComponent),
multi: true
}
]
})
/**
* Component representing a form with a autocomplete functionality
*/
export class OrgUnitInputSuggestionsComponent extends InputSuggestionsComponent implements OnInit {
/**
* The suggestions that should be shown
*/
@Input() suggestions: string[] = [];
ngOnInit() {
if (this.suggestions.length > 0) {
this.value = this.suggestions[0]
}
}
onSubmit(data) {
this.value = data;
this.submitSuggestion.emit(data);
}
onClickSuggestion(data) {
this.value = data;
this.clickSuggestion.emit(data);
this.close();
this.blockReopen = true;
this.queryInput.nativeElement.focus();
return false;
}
}

View File

@@ -1,6 +1,6 @@
<div class="d-flex">
<div class="person-thumbnail pr-2">
<ds-thumbnail [thumbnail]="dso.getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
<!-- <ds-thumbnail [thumbnail]="dso.getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>-->
</div>
<div class="flex-grow-1">
<ds-person-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)" (submitSuggestion)="selectCustom($event)"></ds-person-input-suggestions>

View File

@@ -20,7 +20,15 @@ import { PaginatedList } from '../../../../../core/data/paginated-list';
let personListElementComponent: PersonSearchResultListSubmissionElementComponent;
let fixture: ComponentFixture<PersonSearchResultListSubmissionElementComponent>;
const mockItemWithMetadata: ItemSearchResult = Object.assign(
let mockItemWithMetadata: ItemSearchResult;
let mockItemWithoutMetadata: ItemSearchResult;
let nameVariant;
let mockRelationshipService;
function init() {
mockItemWithMetadata = Object.assign(
new ItemSearchResult(),
{
indexableObject: Object.assign(new Item(), {
@@ -41,7 +49,7 @@ const mockItemWithMetadata: ItemSearchResult = Object.assign(
}
})
});
const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
mockItemWithoutMetadata = Object.assign(
new ItemSearchResult(),
{
indexableObject: Object.assign(new Item(), {
@@ -57,13 +65,15 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
})
});
const nameVariant = "Doe J.";
const mockRelationshipService = {
nameVariant = "Doe J.";
mockRelationshipService = {
getNameVariant: () => observableOf(nameVariant)
};
};
}
describe('PersonSearchResultListElementSubmissionComponent', () => {
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
declarations: [PersonSearchResultListSubmissionElementComponent, TruncatePipe],
providers: [

View File

@@ -11,7 +11,7 @@ import { take } from 'rxjs/operators';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { NameVariantModalComponent } from './name-variant-modal/name-variant-modal.component';
import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component';
import { MetadataValue } from '../../../../../core/shared/metadata.models';
import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';

View File

@@ -81,7 +81,7 @@ import { SelectableListService } from '../../../object-list/selectable-list/sele
import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.component';
import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-disabled.model';
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
import { getAllSucceededRemoteData, getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
import { getAllSucceededRemoteData, getRemoteDataPayload, getSucceededRemoteData, obsLog } from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model';
import { ItemDataService } from '../../../../core/data/item-data.service';
@@ -94,6 +94,7 @@ import { PaginatedList } from '../../../../core/data/paginated-list';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { MetadataValue } from '../../../../core/shared/metadata.models';
import * as uuidv4 from 'uuid/v4';
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
switch (model.type) {
@@ -205,6 +206,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
protected validationService: DynamicFormValidationService,
protected translateService: TranslateService,
private modalService: NgbModal,
private relationService: RelationshipService,
private selectableListService: SelectableListService,
private itemService: ItemDataService,
private relationshipService: RelationshipService,
@@ -216,6 +218,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
}
ngOnInit(): void {
const q = uuidv4();
this.hasRelationLookup = hasValue(this.model.relationship);
if (this.hasRelationLookup) {
this.listId = 'list-' + this.model.relationship.relationshipType;
@@ -226,7 +229,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
this.item$.pipe(
take(1),
switchMap((item: Item) => this.relationshipService.getRelatedItemsByLabel(item, this.model.relationship.relationshipType)),
switchMap((item: Item) => this.relationService.getRelatedItemsByLabel(item, this.model.relationship.relationshipType)),
map((items: RemoteData<PaginatedList<Item>>) => items.payload.page.map((item) => Object.assign(new ItemSearchResult(), { indexableObject: item }))),
).subscribe((relatedItems: SearchResult<Item>[]) => this.selectableListService.select(this.listId, relatedItems));

View File

@@ -0,0 +1,129 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { NgZone, NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf, Subscription } from 'rxjs';
import { DsDynamicLookupRelationModalComponent } from './dynamic-lookup-relation-modal.component';
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
import { RelationshipService } from '../../../../../core/data/relationship.service';
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
import { Store } from '@ngrx/store';
import { Item } from '../../../../../core/shared/item.model';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
import { RelationshipOptions } from '../../models/relationship-options.model';
import { AddRelationshipAction, RemoveRelationshipAction } from './relationship.actions';
describe('DsDynamicLookupRelationModalComponent', () => {
let component: DsDynamicLookupRelationModalComponent;
let fixture: ComponentFixture<DsDynamicLookupRelationModalComponent>;
let item;
let item1;
let item2;
let searchResult1;
let searchResult2;
let listID;
let selection$;
let selectableListService;
let relationship;
let nameVariant;
let metadataField;
function init() {
item = Object.assign(new Item(), { uuid: '7680ca97-e2bd-4398-bfa7-139a8673dc42', metadata: {} });
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
selection$ = observableOf([searchResult1, searchResult2]);
selectableListService = { getSelectableList: () => selection$ };
relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
nameVariant = 'Doe, J.';
metadataField = 'dc.contributor.author';
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
declarations: [DsDynamicLookupRelationModalComponent],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule.forRoot()],
providers: [
{
provide: SelectableListService, useValue: selectableListService
},
{
provide: RelationshipService, useValue: { getNameVariant: () => observableOf(nameVariant) }
},
{ provide: RelationshipTypeService, useValue: {} },
{
provide: Store, useValue: {
dispatch: () => {
}
}
},
{ provide: NgZone, useValue: new NgZone({}) },
NgbActiveModal
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsDynamicLookupRelationModalComponent);
component = fixture.componentInstance;
component.listId = listID;
component.relationshipOptions = relationship;
component.item = item;
component.metadataFields = metadataField;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('close', () => {
beforeEach(() => {
spyOn(component.modal, 'close');
});
it('should call close on the modal', () => {
component.close();
expect(component.modal.close).toHaveBeenCalled();
})
});
describe('select', () => {
beforeEach(() => {
spyOn((component as any).store, 'dispatch');
});
it('should dispatch an AddRelationshipAction for each selected object', () => {
component.select(searchResult1, searchResult2);
const action = new AddRelationshipAction(component.item, searchResult1.indexableObject, relationship.relationshipType, nameVariant);
const action2 = new AddRelationshipAction(component.item, searchResult2.indexableObject, relationship.relationshipType, nameVariant);
expect((component as any).store.dispatch).toHaveBeenCalledWith(action);
expect((component as any).store.dispatch).toHaveBeenCalledWith(action2);
})
});
describe('deselect', () => {
beforeEach(() => {
component.subMap[searchResult1.indexableObject.uuid] = new Subscription();
component.subMap[searchResult2.indexableObject.uuid] = new Subscription();
spyOn((component as any).store, 'dispatch');
});
it('should dispatch an RemoveRelationshipAction for each deselected object', () => {
component.deselect(searchResult1, searchResult2);
const action = new RemoveRelationshipAction(component.item, searchResult1.indexableObject, relationship.relationshipType);
const action2 = new RemoveRelationshipAction(component.item, searchResult2.indexableObject, relationship.relationshipType);
expect((component as any).store.dispatch).toHaveBeenCalledWith(action);
expect((component as any).store.dispatch).toHaveBeenCalledWith(action2);
})
});
});

View File

@@ -66,6 +66,8 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
if (this.relationshipOptions.nameVariants) {
this.context = Context.Workspace;
}
// this.setExistingNameVariants();
}
close() {
@@ -99,7 +101,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
});
}
addNameVariantSubscription(sri: SearchResult<Item>) {
private addNameVariantSubscription(sri: SearchResult<Item>) {
const nameVariant$ = this.relationshipService.getNameVariant(this.listId, sri.indexableObject.uuid);
this.subMap[sri.indexableObject.uuid] = nameVariant$.pipe(
skip(1),
@@ -115,11 +117,12 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
);
}
setExistingNameVariants() {
const virtualMDs$: Observable<MetadataValue[]> = this.item.allMetadata(this.metadataFields).filter((mdValue) => mdValue.isVirtual);
private setExistingNameVariants() {
const virtualMDs: MetadataValue[] = this.item.allMetadata(this.metadataFields).filter((mdValue) => mdValue.isVirtual);
const relatedItemPairs$: Observable<[Item, Item][]> = virtualMDs$.pipe(
switchMap((mds: MetadataValue[]) => combineLatest(mds.map((md: MetadataValue) => this.relationshipService.findById(md.virtualValue).pipe(getSucceededRemoteData(), getRemoteDataPayload())))),
const relatedItemPairs$: Observable<[Item, Item][]> =
combineLatest(virtualMDs.map((md: MetadataValue) => this.relationshipService.findById(md.virtualValue).pipe(getSucceededRemoteData(), getRemoteDataPayload())))
.pipe(
switchMap((relationships: Relationship[]) => combineLatest(relationships.map((relationship: Relationship) =>
combineLatest(
relationship.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()),
@@ -133,7 +136,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
map(([relatedItemPairs,]: [[Item, Item][]]) => relatedItemPairs.map(([left, right]: [Item, Item]) => left.uuid === this.item.uuid ? left : right))
);
combineLatest(virtualMDs$, relatedItems$).pipe(take(1)).subscribe(([virtualMDs, relatedItems]) => {
relatedItems$.pipe(take(1)).subscribe((relatedItems) => {
let index: number = 0;
virtualMDs.forEach(
(md: MetadataValue) => {
@@ -145,7 +148,6 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
)
}
ngOnDestroy() {
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
}

View File

@@ -7,15 +7,29 @@ class NullAction implements Action {
type = null;
}
const listID1 = 'dbfb81de-2930-4de6-ba2e-ea21c8534ee9';
const listID2 = 'd7f2c48d-e1e2-4996-ab8d-e271cabec78a';
const itemID1 = 'd1c81d4f-6b05-4844-986b-372d2e39c6aa';
const itemID2 = 'fe4ca421-d897-417f-9436-9724262d5c69';
const variantList1Item1 = 'Test Name Variant 1';
const variantList1Item1Update = 'Test Name Variant 1 Update';
const variantList1Item2 = 'Test Name Variant 2';
let listID1;
let listID2;
let itemID1;
let itemID2;
let variantList1Item1;
let variantList1Item1Update;
let variantList1Item2;
function init() {
listID1 = 'dbfb81de-2930-4de6-ba2e-ea21c8534ee9';
listID2 = 'd7f2c48d-e1e2-4996-ab8d-e271cabec78a';
itemID1 = 'd1c81d4f-6b05-4844-986b-372d2e39c6aa';
itemID2 = 'fe4ca421-d897-417f-9436-9724262d5c69';
variantList1Item1 = 'Test Name Variant 1';
variantList1Item1Update = 'Test Name Variant 1 Update';
variantList1Item2 = 'Test Name Variant 2';
}
describe('nameVariantReducer', () => {
beforeEach(() => {
init();
});
it('should return the current state when no valid actions have been made', () => {
const state = { [listID1]: { [itemID1]: variantList1Item1 } };

View File

@@ -19,42 +19,64 @@ describe('RelationshipEffects', () => {
let relationEffects: RelationshipEffects;
let actions: Observable<any>;
const testUUID1 = '20e24c2f-a00a-467c-bdee-c929e79bf08d';
const testUUID2 = '7f66a4d0-8557-4e77-8b1e-19930895f10a';
const leftTypeString = 'Publication';
const rightTypeString = 'Person';
const leftType = Object.assign(new ItemType(), {label: leftTypeString});
const rightType = Object.assign(new ItemType(), {label: rightTypeString});
const leftTypeMD = Object.assign(new MetadataValue(), { value: leftTypeString });
const rightTypeMD = Object.assign(new MetadataValue(), { value: rightTypeString });
const relationshipID = '1234';
let testUUID1;
let testUUID2;
let leftTypeString;
let rightTypeString;
let leftType;
let rightType;
let leftTypeMD;
let rightTypeMD;
let relationshipID;
let identifier;
let leftItem = Object.assign(new Item(), {
let leftItem;
let rightItem;
let relationshipType: RelationshipType;
let relationship;
let mockRelationshipService;
let mockRelationshipTypeService;
function init() {
testUUID1 = '20e24c2f-a00a-467c-bdee-c929e79bf08d';
testUUID2 = '7f66a4d0-8557-4e77-8b1e-19930895f10a';
leftTypeString = 'Publication';
rightTypeString = 'Person';
leftType = Object.assign(new ItemType(), {label: leftTypeString});
rightType = Object.assign(new ItemType(), {label: rightTypeString});
leftTypeMD = Object.assign(new MetadataValue(), { value: leftTypeString });
rightTypeMD = Object.assign(new MetadataValue(), { value: rightTypeString });
relationshipID = '1234';
leftItem = Object.assign(new Item(), {
uuid: testUUID1,
metadata: { 'relationship.type': [leftTypeMD] }
});
let rightItem = Object.assign(new Item(), {
rightItem = Object.assign(new Item(), {
uuid: testUUID2,
metadata: { 'relationship.type': [rightTypeMD] }
});
let relationshipType: RelationshipType = Object.assign(new RelationshipType(), {
relationshipType = Object.assign(new RelationshipType(), {
leftwardType: 'isAuthorOfPublication',
rightwardType: 'isPublicationOfAuthor',
leftType: createSuccessfulRemoteDataObject$(leftType),
rightType: createSuccessfulRemoteDataObject$(rightType)
});
let relationship = Object.assign(new Relationship(),
relationship = Object.assign(new Relationship(),
{
uuid: relationshipID,
leftItem: createSuccessfulRemoteDataObject$(leftItem),
rightItem: createSuccessfulRemoteDataObject$(rightItem),
relationshipType: createSuccessfulRemoteDataObject$(relationshipType)
});
const mockRelationshipService = {
mockRelationshipService = {
getRelationshipByItemsAndLabel:
() => observableOf(relationship),
deleteRelationship: () => {
@@ -64,12 +86,13 @@ describe('RelationshipEffects', () => {
/* Do nothing */
}
};
const mockRelationshipTypeService = {
mockRelationshipTypeService = {
getRelationshipTypeByLabelAndTypes:
() => observableOf(relationshipType)
};
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
providers: [
RelationshipEffects,

View File

@@ -51,7 +51,6 @@ export class RelationshipEffects {
take(1)
).subscribe(
(type) => {
debugger;
if (this.initialActionMap[identifier] === type) {
if (type === RelationshipActionTypes.ADD_RELATIONSHIP) {
let nameVariant = (action as AddRelationshipAction).payload.nameVariant;

View File

@@ -0,0 +1,140 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { DsDynamicLookupRelationSearchTabComponent } from './dynamic-lookup-relation-search-tab.component';
import { SearchService } from '../../../../../../core/shared/search/search.service';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { RouteService } from '../../../../../../core/services/route.service';
import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { VarDirective } from '../../../../../utils/var.directive';
import { RelationshipOptions } from '../../../models/relationship-options.model';
import { of as observableOf } from 'rxjs';
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../testing/utils';
import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
import { Item } from '../../../../../../core/shared/item.model';
describe('DsDynamicLookupRelationSearchTabComponent', () => {
let component: DsDynamicLookupRelationSearchTabComponent;
let fixture: ComponentFixture<DsDynamicLookupRelationSearchTabComponent>;
let relationship;
let pSearchOptions;
let item1;
let item2;
let item3;
let searchResult1;
let searchResult2;
let searchResult3;
let listID;
let selection$;
let results;
let selectableListService;
function init() {
relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
pSearchOptions = new PaginatedSearchOptions({});
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
item3 = Object.assign(new Item(), { uuid: 'c3bcbff5-ec0c-4831-8e4c-94b9c933ccac' });
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 });
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
selection$ = observableOf([searchResult1, searchResult2]);
results = new PaginatedList(undefined, [searchResult1, searchResult2, searchResult3]);
selectableListService = jasmine.createSpyObj('selectableListService', ['deselect', 'select', 'deselectAll']);
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
declarations: [DsDynamicLookupRelationSearchTabComponent, VarDirective],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
providers: [
{ provide: SearchService, useValue: { search: () => createSuccessfulRemoteDataObject$(results) } },
{
provide: SelectableListService, useValue: selectableListService
},
{
provide: SearchConfigurationService, useValue: {
paginatedSearchOptions: observableOf(pSearchOptions)
}
},
{
provide: RouteService, useValue: {
setParameter: () => {
// do nothing
}
}
},
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsDynamicLookupRelationSearchTabComponent);
component = fixture.componentInstance;
component.relationship = relationship;
component.selection$ = selection$;
component.listId = listID;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('selectPage', () => {
beforeEach(() => {
spyOn(component.selectObject, 'emit');
component.selectPage([searchResult1, searchResult2, searchResult3]);
});
it('should emit the page filtered from already selected objects and call select on the service for all objects', () => {
expect(component.selectObject.emit).toHaveBeenCalledWith(searchResult3);
expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
});
});
describe('deselectPage', () => {
beforeEach(() => {
spyOn(component.deselectObject, 'emit');
component.deselectPage([searchResult1, searchResult2, searchResult3]);
});
it('should emit the page filtered from not yet selected objects and call select on the service for all objects', () => {
expect(component.deselectObject.emit).toHaveBeenCalledWith(searchResult1, searchResult2);
expect(selectableListService.deselect).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
});
});
describe('selectAll', () => {
beforeEach(() => {
spyOn(component.selectObject, 'emit');
component.selectAll();
});
it('should emit the page filtered from already selected objects and call select on the service for all objects', () => {
expect(component.selectObject.emit).toHaveBeenCalledWith(searchResult3);
expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
});
});
describe('deselectAll', () => {
beforeEach(() => {
spyOn(component.deselectObject, 'emit');
component.deselectAll();
});
it('should emit the page filtered from not yet selected objects and call select on the service for all objects', () => {
expect(component.deselectObject.emit).toHaveBeenCalledWith(searchResult1, searchResult2);
expect(selectableListService.deselectAll).toHaveBeenCalledWith(listID);
});
});
});

View File

@@ -100,7 +100,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0)
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0);
this.selectObject.emit(...filteredPage);
});
this.selectableListService.select(this.listId, page);
@@ -111,7 +111,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) >= 0)
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) >= 0);
this.deselectObject.emit(...filteredPage);
});
this.selectableListService.deselect(this.listId, page);

View File

@@ -0,0 +1,62 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { VarDirective } from '../../../../../utils/var.directive';
import { of as observableOf } from 'rxjs';
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
import { Item } from '../../../../../../core/shared/item.model';
import { DsDynamicLookupRelationSelectionTabComponent } from './dynamic-lookup-relation-selection-tab.component';
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
describe('DsDynamicLookupRelationSelectionTabComponent', () => {
let component: DsDynamicLookupRelationSelectionTabComponent;
let fixture: ComponentFixture<DsDynamicLookupRelationSelectionTabComponent>;
let pSearchOptions = new PaginatedSearchOptions({pagination: new PaginationComponentOptions()});
let item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
let item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
let searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
let searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
let listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
let selection$ = observableOf([searchResult1, searchResult2]);
function init() {
pSearchOptions = new PaginatedSearchOptions({pagination: new PaginationComponentOptions()});
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
selection$ = observableOf([searchResult1, searchResult2]);
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
declarations: [DsDynamicLookupRelationSelectionTabComponent, VarDirective],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
providers: [
{
provide: SearchConfigurationService, useValue: {
paginatedSearchOptions: observableOf(pSearchOptions)
}
}
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsDynamicLookupRelationSelectionTabComponent);
component = fixture.componentInstance;
component.selection$ = selection$;
component.listId = listID;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -382,7 +382,6 @@ describe('FormComponent test suite', () => {
describe('', () => {
beforeEach(() => {
formFixture = TestBed.createComponent(FormComponent);
store = TestBed.get(Store);
formComp = formFixture.componentInstance; // FormComponent test instance

View File

@@ -14,9 +14,11 @@ describe('InputSuggestionsComponent', () => {
let fixture: ComponentFixture<InputSuggestionsComponent>;
let de: DebugElement;
let el: HTMLElement;
const suggestions = [{displayValue: 'suggestion uno', value: 'suggestion uno'}, {displayValue: 'suggestion dos', value: 'suggestion dos'}, {displayValue: 'suggestion tres', value: 'suggestion tres'}];
let suggestions;
beforeEach(async(() => {
suggestions = [{displayValue: 'suggestion uno', value: 'suggestion uno'}, {displayValue: 'suggestion dos', value: 'suggestion dos'}, {displayValue: 'suggestion tres', value: 'suggestion tres'}];
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule, FormsModule],
declarations: [InputSuggestionsComponent],