From 43f5e08530c7a8838eb028131df65c69c3069d24 Mon Sep 17 00:00:00 2001 From: Matteo Perelli Date: Wed, 24 Jun 2020 10:16:30 +0200 Subject: [PATCH] External source import completed with tests --- .../import-external-page.component.ts | 2 +- .../import-external-page.module.ts | 2 - .../collection-dropdown.component.ts | 2 +- .../mocks/external-source.service.mock.ts | 53 +++++- src/app/shared/shared.module.ts | 3 +- .../shared/testing/submission-service.stub.ts | 1 + ...-import-external-collection.component.html | 11 ++ ...-import-external-collection.component.scss | 3 + ...port-external-collection.component.spec.ts | 89 ++++++++++ ...on-import-external-collection.component.ts | 40 +++++ ...ion-import-external-preview.component.html | 4 +- ...-import-external-preview.component.spec.ts | 153 +++++++++++++++++ ...ssion-import-external-preview.component.ts | 44 ++++- ...mport-external-searchbar.component.spec.ts | 102 ++++++++--- ...ion-import-external-searchbar.component.ts | 23 ++- ...bmission-import-external.component.spec.ts | 159 ++++++++++++++++++ .../submission-import-external.component.ts | 2 +- src/app/submission/submission.module.ts | 7 +- src/app/submission/submission.service.spec.ts | 13 ++ src/app/submission/submission.service.ts | 18 ++ src/assets/i18n/en.json5 | 4 + 21 files changed, 681 insertions(+), 54 deletions(-) create mode 100644 src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.html create mode 100644 src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.scss create mode 100644 src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts create mode 100644 src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts create mode 100644 src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.spec.ts diff --git a/src/app/+import-external-page/import-external-page.component.ts b/src/app/+import-external-page/import-external-page.component.ts index c205482cb3..00709dad16 100644 --- a/src/app/+import-external-page/import-external-page.component.ts +++ b/src/app/+import-external-page/import-external-page.component.ts @@ -1,4 +1,4 @@ -import { Component, Injector, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; /** * Component representing the external import page of the submission. diff --git a/src/app/+import-external-page/import-external-page.module.ts b/src/app/+import-external-page/import-external-page.module.ts index c81e80615f..8dae7c2f88 100644 --- a/src/app/+import-external-page/import-external-page.module.ts +++ b/src/app/+import-external-page/import-external-page.module.ts @@ -4,8 +4,6 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { CoreModule } from '../core/core.module'; import { ImportExternalRoutingModule } from './import-external-routing.module'; -import { SubmissionImportExternalComponent } from '../submission/import-external/submission-import-external.component'; -import { SubmissionImportExternalSearchbarComponent } from '../submission/import-external/import-external-searchbar/submission-import-external-searchbar.component'; import { SubmissionModule } from '../submission/submission.module'; @NgModule({ diff --git a/src/app/shared/collection-dropdown/collection-dropdown.component.ts b/src/app/shared/collection-dropdown/collection-dropdown.component.ts index 0e9a4ab629..49af24ad3d 100644 --- a/src/app/shared/collection-dropdown/collection-dropdown.component.ts +++ b/src/app/shared/collection-dropdown/collection-dropdown.component.ts @@ -24,7 +24,7 @@ interface CollectionListEntryItem { /** * An interface to represent an entry in the collection list */ -interface CollectionListEntry { +export interface CollectionListEntry { communities: CollectionListEntryItem[], collection: CollectionListEntryItem } diff --git a/src/app/shared/mocks/external-source.service.mock.ts b/src/app/shared/mocks/external-source.service.mock.ts index 80e40512c5..4edf9efb14 100644 --- a/src/app/shared/mocks/external-source.service.mock.ts +++ b/src/app/shared/mocks/external-source.service.mock.ts @@ -1,12 +1,59 @@ -// import { ExternalSourceService } from '../../core/data/external-source.service'; +import { ExternalSourceService } from '../../core/data/external-source.service'; +import { ExternalSource } from '../../core/shared/external-source.model'; +import { ResourceType } from '../../core/shared/resource-type'; + +export const externalSourceOrcid: ExternalSource = { + type: new ResourceType('externalsource'), + id: 'orcid', + name: 'orcid', + hierarchical: false, + _links: { + entries: { + href: "https://dspace7.4science.cloud/server/api/integration/externalsources/orcid/entries" + }, + self: { + href: "https://dspace7.4science.cloud/server/api/integration/externalsources/orcid" + } + } +}; + +export const externalSourceCiencia: ExternalSource = { + type: new ResourceType('externalsource'), + id: 'ciencia', + name: 'ciencia', + hierarchical: false, + _links: { + entries: { + href: "https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia/entries" + }, + self: { + href: "https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia" + } + } +}; + +export const externalSourceMyStaffDb: ExternalSource = { + type: new ResourceType('externalsource'), + id: 'my_staff_db', + name: 'my_staff_db', + hierarchical: false, + _links: { + entries: { + href: "https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entries" + }, + self: { + href: "https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db" + } + } +}; /** * Mock for [[ExternalSourceService]] */ -/*export function getMockExternalSourceService(): +export function getMockExternalSourceService(): ExternalSourceService { return jasmine.createSpyObj('ExternalSourceService', { getAllExternalSources: jasmine.createSpy('getAllExternalSources'), getExternalSourceEntries: jasmine.createSpy('getExternalSourceEntries'), }); -}*/ +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 8ef3f91257..e83e1179f6 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -461,7 +461,8 @@ const ENTRY_COMPONENTS = [ ClaimedTaskActionsApproveComponent, ClaimedTaskActionsRejectComponent, ClaimedTaskActionsReturnToPoolComponent, - ClaimedTaskActionsEditMetadataComponent + ClaimedTaskActionsEditMetadataComponent, + CollectionDropdownComponent ]; const SHARED_ITEM_PAGE_COMPONENTS = [ diff --git a/src/app/shared/testing/submission-service.stub.ts b/src/app/shared/testing/submission-service.stub.ts index a330fa3eee..35c3ddfee0 100644 --- a/src/app/shared/testing/submission-service.stub.ts +++ b/src/app/shared/testing/submission-service.stub.ts @@ -2,6 +2,7 @@ export class SubmissionServiceStub { changeSubmissionCollection = jasmine.createSpy('changeSubmissionCollection'); createSubmission = jasmine.createSpy('createSubmission'); + createSubmissionFromExternalSource = jasmine.createSpy('createSubmissionFromExternalSource'); depositSubmission = jasmine.createSpy('depositSubmission'); discardSubmission = jasmine.createSpy('discardSubmission'); dispatchInit = jasmine.createSpy('dispatchInit'); diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.html b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.html new file mode 100644 index 0000000000..73b41378c8 --- /dev/null +++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.scss b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.scss new file mode 100644 index 0000000000..1a70081367 --- /dev/null +++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.scss @@ -0,0 +1,3 @@ +.close:focus { + outline: none !important; +} diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts new file mode 100644 index 0000000000..5002c4f94a --- /dev/null +++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts @@ -0,0 +1,89 @@ +import { Component, NO_ERRORS_SCHEMA, EventEmitter } from '@angular/core'; +import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { createTestComponent } from '../../../shared/testing/utils.test'; +import { SubmissionImportExternalCollectionComponent } from './submission-import-external-collection.component'; +import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +describe('SubmissionImportExternalCollectionComponent test suite', () => { + let comp: SubmissionImportExternalCollectionComponent; + let compAsAny: any; + let fixture: ComponentFixture; + + beforeEach(async (() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ], + declarations: [ + SubmissionImportExternalCollectionComponent, + TestComponent, + ], + providers: [ + NgbActiveModal, + SubmissionImportExternalCollectionComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents().then(); + })); + + // First test to check the correct component creation + describe('', () => { + let testComp: TestComponent; + let testFixture: ComponentFixture; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + testComp = testFixture.componentInstance; + }); + + afterEach(() => { + testFixture.destroy(); + }); + + it('should create SubmissionImportExternalCollectionComponent', inject([SubmissionImportExternalCollectionComponent], (app: SubmissionImportExternalCollectionComponent) => { + expect(app).toBeDefined(); + })); + }); + + describe('', () => { + beforeEach(() => { + fixture = TestBed.createComponent(SubmissionImportExternalCollectionComponent); + comp = fixture.componentInstance; + compAsAny = comp; + }); + + afterEach(() => { + fixture.destroy(); + comp = null; + compAsAny = null; + }); + + it('The variable \'selectedEvent\' should be assigned', () => { + const event = new EventEmitter(); + comp.selectObject(event); + + expect(comp.selectedEvent).toEqual(event); + }); + + it('The variable \'selectedEvent\' should be assigned', () => { + spyOn(compAsAny.activeModal, 'dismiss'); + comp.closeCollectionModal(); + + expect(compAsAny.activeModal.dismiss).toHaveBeenCalled(); + }); + }); +}); + +// declare a test component +@Component({ + selector: 'ds-test-cmp', + template: `` +}) +class TestComponent { + +} diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts new file mode 100644 index 0000000000..cbac0cb710 --- /dev/null +++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts @@ -0,0 +1,40 @@ +import { Component, Output, EventEmitter } from '@angular/core'; +import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +/** + * Wrap component for 'ds-collection-dropdown'. + */ +@Component({ + selector: 'ds-submission-import-external-collection', + styleUrls: ['./submission-import-external-collection.component.scss'], + templateUrl: './submission-import-external-collection.component.html' +}) +export class SubmissionImportExternalCollectionComponent { + /** + * The event passed by 'ds-collection-dropdown'. + */ + @Output() public selectedEvent = new EventEmitter(); + + /** + * Initialize the component variables. + * @param {NgbActiveModal} activeModal + */ + constructor( + private activeModal: NgbActiveModal + ) { } + + /** + * This method populates the 'selectedEvent' variable. + */ + public selectObject(event): void { + this.selectedEvent.emit(event); + } + + /** + * This method closes the modal. + */ + public closeCollectionModal(): void { + this.activeModal.dismiss(false); + } +} diff --git a/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.html b/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.html index 29f1804f1f..0f155286ce 100644 --- a/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.html +++ b/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.html @@ -30,9 +30,9 @@ diff --git a/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.spec.ts b/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.spec.ts new file mode 100644 index 0000000000..66dfc72e6b --- /dev/null +++ b/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.spec.ts @@ -0,0 +1,153 @@ +import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { Router } from '@angular/router'; +import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { of as observableOf, of } from 'rxjs/internal/observable/of'; +import { SubmissionImportExternalPreviewComponent } from './submission-import-external-preview.component'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { RouterStub } from '../../../shared/testing/router.stub'; +import { SubmissionService } from '../../submission.service'; +import { createTestComponent } from '../../../shared/testing/utils.test'; +import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { ExternalSourceEntry } from '../../../core/shared/external-source-entry.model'; +import { Metadata } from '../../../core/shared/metadata.utils'; +import { SubmissionImportExternalCollectionComponent } from '../import-external-collection/submission-import-external-collection.component'; +import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component'; + +describe('SubmissionImportExternalPreviewComponent test suite', () => { + let comp: SubmissionImportExternalPreviewComponent; + let compAsAny: any; + let fixture: ComponentFixture; + let submissionServiceStub: SubmissionServiceStub; + const externalEntry = Object.assign(new ExternalSourceEntry(), { + id: '0001-0001-0001-0001', + display: 'John Doe', + value: 'John, Doe', + metadata: { + 'dc.identifier.uri': [ + { + value: 'https://orcid.org/0001-0001-0001-0001' + } + ] + }, + _links: { self: { href: 'http://test-rest.com/server/api/integration/externalSources/orcidV2/entryValues/0000-0003-4851-8004' } } + }); + + beforeEach(async (() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot() + ], + declarations: [ + SubmissionImportExternalPreviewComponent, + TestComponent + ], + providers: [ + { provide: Router, useValue: new RouterStub() }, + { provide: SubmissionService, useValue: new SubmissionServiceStub() }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + NgbModal, + NgbActiveModal, + SubmissionImportExternalPreviewComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents().then(); + })); + + // First test to check the correct component creation + describe('', () => { + let testComp: TestComponent; + let testFixture: ComponentFixture; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + testComp = testFixture.componentInstance; + }); + + afterEach(() => { + testFixture.destroy(); + }); + + it('should create SubmissionImportExternalComponent', inject([SubmissionImportExternalPreviewComponent], (app: SubmissionImportExternalPreviewComponent) => { + expect(app).toBeDefined(); + })); + }); + + describe('', () => { + beforeEach(() => { + fixture = TestBed.createComponent(SubmissionImportExternalPreviewComponent); + comp = fixture.componentInstance; + compAsAny = comp; + submissionServiceStub = TestBed.get(SubmissionService); + }); + + afterEach(() => { + fixture.destroy(); + comp = null; + compAsAny = null; + }); + + it('Should init component properly', () => { + comp.externalSourceEntry = externalEntry; + const expected = [ + { key: 'dc.identifier.uri', value: Metadata.first(comp.externalSourceEntry.metadata, 'dc.identifier.uri') } + ]; + comp.ngOnInit(); + fixture.detectChanges(); + + expect(comp.metadataList).toEqual(expected); + }); + + it('Should close the modal calling \'activeModal.dismiss\'', () => { + spyOn(compAsAny.activeModal, 'dismiss'); + comp.closeMetadataModal(); + + expect(compAsAny.activeModal.dismiss).toHaveBeenCalled(); + }); + + it('Should start the import process opening a modal', () => { + const emittedEvent: CollectionListEntry = { + communities: [ + { + id: 'dummy', + uuid: 'dummy', + name: 'dummy', + } + ], + collection: { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + name: 'Collection 1', + } + }; + const submissionObjects = [ + { id: 'jk11k13o-9v4z-632i-sr88-wq071n0h1d47' } + ]; + comp.externalSourceEntry = externalEntry; + spyOn(compAsAny.modalService, 'open').and.returnValue({componentInstance: { selectedEvent: observableOf(emittedEvent)}}); + spyOn(comp, 'closeMetadataModal'); + submissionServiceStub.createSubmissionFromExternalSource.and.returnValue(observableOf(submissionObjects)); + spyOn(compAsAny.router, 'navigateByUrl'); + comp.import(); + + expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalCollectionComponent, { size: 'lg' }); + expect(comp.closeMetadataModal).toHaveBeenCalled(); + expect(compAsAny.submissionService.createSubmissionFromExternalSource).toHaveBeenCalledWith(externalEntry._links.self.href, emittedEvent.collection.id); + expect(compAsAny.router.navigateByUrl).toHaveBeenCalledWith('/workspaceitems/' + submissionObjects[0].id + '/edit'); + }); + }); +}); + +// declare a test component +@Component({ + selector: 'ds-test-cmp', + template: `` +}) +class TestComponent { + +} diff --git a/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.ts b/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.ts index de86bba3b1..8294ec66ba 100644 --- a/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.ts +++ b/src/app/submission/import-external/import-external-preview/submission-import-external-preview.component.ts @@ -1,8 +1,15 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import { NgbActiveModal, NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ExternalSourceEntry } from '../../../core/shared/external-source-entry.model'; import { MetadataValue } from '../../../core/shared/metadata.models'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component'; +import { mergeMap } from 'rxjs/operators'; +import { SubmissionService } from '../../submission.service'; +import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { SubmissionImportExternalCollectionComponent } from '../import-external-collection/submission-import-external-collection.component'; /** * This component display a preview of an external source item. @@ -29,10 +36,17 @@ export class SubmissionImportExternalPreviewComponent implements OnInit { /** * Initialize the component variables. * @param {NgbActiveModal} activeModal + * @param {SubmissionService} submissionService + * @param {NgbModal} modalService + * @param {Router} router + * @param {NotificationsService} notificationService */ constructor( private activeModal: NgbActiveModal, - private modalService: NgbModal + private submissionService: SubmissionService, + private modalService: NgbModal, + private router: Router, + private notificationService: NotificationsService ) { } /** @@ -57,14 +71,30 @@ export class SubmissionImportExternalPreviewComponent implements OnInit { } /** - * Start the import of an entry by opening up an import modal window. - * @param entry The entry to import + * Start the import of an entry by opening up a collection choice modal window. */ - public import(entry): void { - this.modalRef = this.modalService.open(SubmissionImportExternalPreviewComponent, { + public import(): void { + this.modalRef = this.modalService.open(SubmissionImportExternalCollectionComponent, { size: 'lg', }); - const modalComp = this.modalRef.componentInstance; - modalComp.externalSourceEntry = entry; + this.closeMetadataModal(); + + this.modalRef.componentInstance.selectedEvent.pipe( + mergeMap((collectionListEntry: CollectionListEntry) => { + return this.submissionService.createSubmissionFromExternalSource(this.externalSourceEntry._links.self.href, collectionListEntry.collection.id); + }) + ).subscribe((submissionObjects: SubmissionObject[]) => { + let isValid = false + if (submissionObjects.length === 1) { + if (submissionObjects[0] !== null) { + isValid = true; + this.router.navigateByUrl('/workspaceitems/' + submissionObjects[0].id + '/edit'); + } + } + if (!isValid) { + this.notificationService.error('submission.import-external.preview.error.import.title', 'submission.import-external.preview.error.import.body'); + } + this.modalRef.close(); + }); } } diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.spec.ts b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.spec.ts index 66ce798ed9..1f77374932 100644 --- a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.spec.ts +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.spec.ts @@ -1,13 +1,18 @@ -// import { Component, NO_ERRORS_SCHEMA, ChangeDetectorRef } from '@angular/core'; -// import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing'; -// import { TranslateModule } from '@ngx-translate/core'; -// import { SubmissionImportExternalSearchbarComponent } from './submission-import-external-searchbar.component'; -// import { ExternalSourceService } from '../../../core/data/external-source.service'; -// import { createTestComponent } from '../../../shared/testing/utils.test'; -// import { getMockExternalSourceService } from '../../../shared/mocks/external-source.service.mock'; -// import { SubmissionModule } from '../../submission.module'; +import { Component, NO_ERRORS_SCHEMA, ChangeDetectorRef } from '@angular/core'; +import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; +import { SubmissionImportExternalSearchbarComponent, SourceElement } from './submission-import-external-searchbar.component'; +import { ExternalSourceService } from '../../../core/data/external-source.service'; +import { createTestComponent } from '../../../shared/testing/utils.test'; +import { getMockExternalSourceService, externalSourceOrcid, externalSourceCiencia, externalSourceMyStaffDb } from '../../../shared/mocks/external-source.service.mock'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { ExternalSource } from '../../../core/shared/external-source.model'; +import { FindListOptions } from '../../../core/data/request.models'; -/*describe('SubmissionImportExternalSearchbarComponent test suite', () => { +describe('SubmissionImportExternalSearchbarComponent test suite', () => { let comp: SubmissionImportExternalSearchbarComponent; let compAsAny: any; let fixture: ComponentFixture; @@ -15,7 +20,6 @@ beforeEach(async (() => { TestBed.configureTestingModule({ imports: [ - SubmissionModule, TranslateModule.forRoot(), ], declarations: [ @@ -51,16 +55,25 @@ it('should create SubmissionImportExternalSearchbarComponent', inject([SubmissionImportExternalSearchbarComponent], (app: SubmissionImportExternalSearchbarComponent) => { expect(app).toBeDefined(); })); - });*/ + }); + + describe('', () => { + let sourceList: SourceElement[]; + let paginatedList: PaginatedList; - /*describe('', () => { beforeEach(() => { fixture = TestBed.createComponent(SubmissionImportExternalSearchbarComponent); comp = fixture.componentInstance; compAsAny = comp; - // compAsAny.externalService.getAllExternalSources.and.returnValue(observableOf([ - - // ])); + const pageInfo = new PageInfo(); + paginatedList = new PaginatedList(pageInfo, [externalSourceOrcid, externalSourceCiencia, externalSourceMyStaffDb]); + const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + compAsAny.externalService.getAllExternalSources.and.returnValue(observableOf(paginatedListRD)); + sourceList = [ + {id: 'orcid', name: 'orcid'}, + {id: 'ciencia', name: 'ciencia'}, + {id: 'my_staff_db', name: 'my_staff_db'}, + ]; }); afterEach(() => { @@ -69,16 +82,61 @@ compAsAny = null; }); - it('Should init component properly', () => { + it('Should init component properly (without initExternalSourceData)', () => { + comp.initExternalSourceData = { sourceId: '', query: '' }; comp.ngOnInit(); fixture.detectChanges(); - expect(comp.selectedElement) - expect(compAsAny.pageInfo) - expect(comp.sourceList) + expect(comp.selectedElement).toEqual(sourceList[0]); + expect(compAsAny.pageInfo).toEqual(paginatedList.pageInfo); + expect(comp.sourceList).toEqual(sourceList); }); - });*/ -/* + + it('Should init component properly (with initExternalSourceData populated)', () => { + comp.initExternalSourceData = { query: 'dummy', sourceId: 'ciencia' }; + comp.ngOnInit(); + fixture.detectChanges(); + + expect(comp.selectedElement).toEqual(sourceList[1]); + expect(compAsAny.pageInfo).toEqual(paginatedList.pageInfo); + expect(comp.sourceList).toEqual(sourceList); + }); + + it('Variable \'selectedElement\' should be assigned', () => { + const selectedElement = {id: 'orcid', name: 'orcid'}; + comp.makeSourceSelection(selectedElement); + expect(comp.selectedElement).toEqual(selectedElement); + }); + + it('Should load additional external sources', () => { + comp.sourceListLoading = false; + compAsAny.pageInfo = new PageInfo({ + elementsPerPage: 3, + totalElements: 6, + totalPages: 2, + currentPage: 0 + }); + compAsAny.findListOptions = Object.assign({}, new FindListOptions(), { + elementsPerPage: 3, + currentPage: 0, + }); + comp.sourceList = sourceList; + const expected = sourceList.concat(sourceList); + comp.onScroll(); + + expect(comp.sourceList).toEqual(expected); + }); + + it('The \'search\' method should call \'emit\'', () => { + comp.selectedElement = { id: 'orcidV2', name: 'orcidV2' }; + comp.searchString = 'dummy'; + const expected = { sourceId: comp.selectedElement.id, query: comp.searchString }; + spyOn(comp.externalSourceData, 'emit'); + comp.search(); + + expect(comp.externalSourceData.emit).toHaveBeenCalledWith(expected); + }); + }); }); // declare a test component @@ -88,4 +146,4 @@ }) class TestComponent { -}*/ +} diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts index cd2f24eb2c..efbcbad60c 100644 --- a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts @@ -10,13 +10,12 @@ import { RemoteData } from '../../../core/data/remote-data'; import { PageInfo } from '../../../core/shared/page-info.model'; import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import { FindListOptions } from '../../../core/data/request.models'; -import { isEmpty } from '../../../shared/empty.util'; import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; /** * Interface for the selected external source element. */ -interface SourceElement { +export interface SourceElement { id: string; name: string; } @@ -124,14 +123,14 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit { /** * Set the selected external source. */ - makeSourceSelection(source) { + public makeSourceSelection(source): void { this.selectedElement = source; } /** * Load the next pages of external sources. */ - onScroll() { + public onScroll(): void { if (!this.sourceListLoading && this.pageInfo.currentPage <= this.pageInfo.totalPages) { this.sourceListLoading = true; this.findListOptions = Object.assign({}, new FindListOptions(), { @@ -145,21 +144,21 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit { const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); return observableOf(paginatedListRD); }), - tap(() => this.sourceListLoading = false)) - .subscribe((externalSource: RemoteData>) => { - externalSource.payload.page.forEach((element) => { - this.sourceList.push({ id: element.id, name: element.name }); - }) - this.pageInfo = externalSource.payload.pageInfo; - this.cdr.detectChanges(); + tap(() => this.sourceListLoading = false) + ).subscribe((externalSource: RemoteData>) => { + externalSource.payload.page.forEach((element) => { + this.sourceList.push({ id: element.id, name: element.name }); }) + this.pageInfo = externalSource.payload.pageInfo; + this.cdr.detectChanges(); + }) } } /** * Passes the search parameters to the parent component. */ - public search() { + public search(): void { this.externalSourceData.emit({ sourceId: this.selectedElement.id, query: this.searchString }); } } diff --git a/src/app/submission/import-external/submission-import-external.component.spec.ts b/src/app/submission/import-external/submission-import-external.component.spec.ts index e69de29bb2..6b5ec05171 100644 --- a/src/app/submission/import-external/submission-import-external.component.spec.ts +++ b/src/app/submission/import-external/submission-import-external.component.spec.ts @@ -0,0 +1,159 @@ +import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { Router } from '@angular/router'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { of as observableOf, of } from 'rxjs/internal/observable/of'; +import { SubmissionImportExternalComponent } from './submission-import-external.component'; +import { ExternalSourceService } from '../../core/data/external-source.service'; +import { getMockExternalSourceService } from '../../shared/mocks/external-source.service.mock'; +import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; +import { RouteService } from '../../core/services/route.service'; +import { createTestComponent, createPaginatedList } from '../../shared/testing/utils.test'; +import { RouterStub } from '../../shared/testing/router.stub'; +import { VarDirective } from '../../shared/utils/var.directive'; +import { routeServiceStub } from '../../shared/testing/route-service.stub'; +import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { ExternalSourceEntry } from '../../core/shared/external-source-entry.model'; +import { SubmissionImportExternalPreviewComponent } from './import-external-preview/submission-import-external-preview.component'; + +describe('SubmissionImportExternalComponent test suite', () => { + let comp: SubmissionImportExternalComponent; + let compAsAny: any; + let fixture: ComponentFixture; + const mockSearchOptions = of(new PaginatedSearchOptions({ + pagination: Object.assign(new PaginationComponentOptions(), { + pageSize: 10, + currentPage: 0 + }) + })); + const searchConfigServiceStub = { + paginatedSearchOptions: mockSearchOptions + }; + + beforeEach(async (() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot() + ], + declarations: [ + SubmissionImportExternalComponent, + TestComponent, + VarDirective + ], + providers: [ + { provide: ExternalSourceService, useClass: getMockExternalSourceService }, + { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: new RouterStub() }, + NgbModal, + SubmissionImportExternalComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents().then(); + })); + + // First test to check the correct component creation + describe('', () => { + let testComp: TestComponent; + let testFixture: ComponentFixture; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + testComp = testFixture.componentInstance; + }); + + afterEach(() => { + testFixture.destroy(); + }); + + it('should create SubmissionImportExternalComponent', inject([SubmissionImportExternalComponent], (app: SubmissionImportExternalComponent) => { + expect(app).toBeDefined(); + })); + }); + + describe('', () => { + beforeEach(() => { + fixture = TestBed.createComponent(SubmissionImportExternalComponent); + comp = fixture.componentInstance; + compAsAny = comp; + }); + + afterEach(() => { + fixture.destroy(); + comp = null; + compAsAny = null; + }); + + it('Should init component properly (without route data)', () => { + const expectedEntries = createSuccessfulRemoteDataObject(createPaginatedList([])); + spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValue(observableOf('')); + comp.ngOnInit(); + fixture.detectChanges(); + + expect(comp.routeData).toEqual({ sourceId: '', query: '' }); + expect(comp.isLoading$.value).toBe(false); + expect(comp.entriesRD$.value).toEqual(expectedEntries); + }); + + it('Should init component properly (with route data)', () => { + const expectedEntries = createSuccessfulRemoteDataObject(createPaginatedList([])); + const searchOptions = new PaginatedSearchOptions({ + pagination: Object.assign(new PaginationComponentOptions(), { + pageSize: 10, + currentPage: 0 + }) + }); + spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValue(observableOf('dummy')); + comp.ngOnInit(); + fixture.detectChanges(); + + expect(comp.routeData).toEqual({ sourceId: 'dummy', query: 'dummy' }); + expect(comp.isLoading$.value).toBe(true); + expect(comp.entriesRD$.value).toEqual(expectedEntries); + expect(compAsAny.externalService.getExternalSourceEntries).toHaveBeenCalledWith('dummy', searchOptions); + }); + + it('Should call \'router.navigate\'', () => { + const event = { sourceId: 'orcidV2', query: 'dummy' }; + comp.getExternalsourceData(event); + + expect(compAsAny.router.navigate).toHaveBeenCalledWith([], { queryParams: { source: event.sourceId, query: event.query }, replaceUrl: true }); + }); + + it('Entry should be passed to the component loaded inside the modal', () => { + const entry = Object.assign(new ExternalSourceEntry(), { + id: '0001-0001-0001-0001', + display: 'John Doe', + value: 'John, Doe', + metadata: { + 'dc.identifier.uri': [ + { + value: 'https://orcid.org/0001-0001-0001-0001' + } + ] + } + }); + spyOn(compAsAny.modalService, 'open').and.returnValue({componentInstance: { externalSourceEntry: null}}); + comp.import(entry); + + expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalPreviewComponent, { size: 'lg' }); + expect(comp.modalRef.componentInstance.externalSourceEntry).toEqual(entry); + }); + }); + +}); + +// declare a test component +@Component({ + selector: 'ds-test-cmp', + template: `` +}) +class TestComponent { + +} diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index 47ae781baa..70fdd5f477 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -70,7 +70,7 @@ export class SubmissionImportExternalComponent implements OnInit { /** * The modal for the entry preview */ - modalRef: NgbModalRef; + public modalRef: NgbModalRef; /** * Initialize the component variables. diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 7ff8b98d9e..4e5e5bc6da 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -31,6 +31,7 @@ import { storeModuleConfig } from '../app.reducer'; import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component'; import { SubmissionImportExternalSearchbarComponent } from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; import { SubmissionImportExternalPreviewComponent } from './import-external/import-external-preview/submission-import-external-preview.component'; +import { SubmissionImportExternalCollectionComponent } from './import-external/import-external-collection/submission-import-external-collection.component'; @NgModule({ imports: [ @@ -60,14 +61,16 @@ import { SubmissionImportExternalPreviewComponent } from './import-external/impo SubmissionSectionUploadFileViewComponent, SubmissionImportExternalComponent, SubmissionImportExternalSearchbarComponent, - SubmissionImportExternalPreviewComponent + SubmissionImportExternalPreviewComponent, + SubmissionImportExternalCollectionComponent ], entryComponents: [ SubmissionSectionUploadComponent, SubmissionSectionformComponent, SubmissionSectionLicenseComponent, SubmissionSectionContainerComponent, - SubmissionImportExternalPreviewComponent + SubmissionImportExternalPreviewComponent, + SubmissionImportExternalCollectionComponent ], exports: [ SubmissionEditComponent, diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index eb7538ec69..9cb678dbb3 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -410,6 +410,19 @@ describe('SubmissionService test suite', () => { }); }); + describe('createSubmissionFromExternalSource', () => { + it('should deposit submission', () => { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + + service.createSubmissionFromExternalSource(selfUrl, collectionId); + + expect((service as any).restService.postToEndpoint).toHaveBeenCalledWith('workspaceitems', selfUrl, null, options, collectionId); + }); + }); + describe('depositSubmission', () => { it('should deposit submission', () => { const options: HttpOptions = Object.create({}); diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index f06f6ea069..7aa02c6c07 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -113,6 +113,24 @@ export class SubmissionService { catchError(() => observableOf({} as SubmissionObject))) } + /** + * Perform a REST call to deposit a workspaceitem and return response + * + * @param selfUrl + * The workspaceitem self url + * @param collectionId + * Optional collection id + * @return Observable + * observable of SubmissionObject + */ + createSubmissionFromExternalSource(selfUrl: string, collectionId?: string): Observable { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + return this.restService.postToEndpoint(this.workspaceLinkPath, selfUrl, null, options, collectionId) as Observable; + } + /** * Perform a REST call to deposit a workspaceitem and return response * diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 3e2994142d..fdacf7cd44 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2432,6 +2432,10 @@ "submission.import-external.preview.button.import": "Start submission", + "submission.import-external.preview.error.import.title": "Submission error", + + "submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.", + "submission.sections.describe.relationship-lookup.close": "Close",