From 8013260f78024be04d1207c6df67bbda59d394e7 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 22 Dec 2020 13:28:44 +0100 Subject: [PATCH] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Task Service implementation. ReloadableAction abstraction. --- ...claimed-task-actions-abstract.component.ts | 5 +- ...imed-task-actions-loader.component.spec.ts | 21 +- .../mydspace-reloadable-actions.spec.ts | 260 ++++++++++++++++++ .../mydspace-reloadable-actions.ts | 10 +- .../pool-task-actions.component.spec.ts | 153 +---------- 5 files changed, 294 insertions(+), 155 deletions(-) create mode 100644 src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts index 9e75cf1f5e..eb43a33a20 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts @@ -79,7 +79,10 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload * Retrieve the itemUuid. */ initReloadAnchor() { - (this.object as any).workflowitem.pipe( + if (!(this.object as any).workflowitem) { + return; + } + this.object.workflowitem.pipe( getFirstSucceededRemoteDataPayload(), switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) )) diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts index 036b08634a..fc37cf4fea 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts @@ -1,6 +1,6 @@ import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChangeDetectionStrategy, ComponentFactoryResolver, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, ComponentFactoryResolver, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import * as decorators from './claimed-task-actions-decorator'; import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; @@ -8,6 +8,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskActionsEditMetadataComponent } from '../edit-metadata/claimed-task-actions-edit-metadata.component'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { spyOnExported } from '../../../testing/utils.test'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; + +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); describe('ClaimedTaskActionsLoaderComponent', () => { let comp: ClaimedTaskActionsLoaderComponent; @@ -23,6 +36,12 @@ describe('ClaimedTaskActionsLoaderComponent', () => { schemas: [NO_ERRORS_SCHEMA], providers: [ { provide: ClaimedTaskDataService, useValue: {} }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: {} }, ComponentFactoryResolver ] }).overrideComponent(ClaimedTaskActionsLoaderComponent, { diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts new file mode 100644 index 0000000000..609dad39d2 --- /dev/null +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts @@ -0,0 +1,260 @@ +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { PoolTaskDataService } from '../../core/tasks/pool-task-data.service'; +import { ClaimedTaskDataService } from '../../core/tasks/claimed-task-data.service'; +import { PoolTaskActionsComponent } from './pool-task/pool-task-actions.component'; +import { PoolTask } from '../../core/tasks/models/pool-task-object.model'; +import { NotificationsServiceStub } from '../testing/notifications-service.stub'; +import { RouterStub } from '../testing/router.stub'; +import { getMockSearchService } from '../mocks/search-service.mock'; +import { getMockRequestService } from '../mocks/request.service.mock'; +import { Item } from '../../core/shared/item.model'; +import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../remote-data.utils'; +import { WorkflowItem } from '../../core/submission/models/workflowitem.model'; +import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; +import { NotificationsService } from '../notifications/notifications.service'; +import { SearchService } from '../../core/shared/search/search.service'; +import { RequestService } from '../../core/data/request.service'; +import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response'; + +let mockDataService: PoolTaskDataService; +let mockClaimedTaskDataService: ClaimedTaskDataService; + +let component: PoolTaskActionsComponent; +let fixture: ComponentFixture; + +let mockObject: PoolTask; +let notificationsServiceStub: NotificationsServiceStub; +let router: RouterStub; + +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +const item = Object.assign(new Item(), { + bundles: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.type': [ + { + language: null, + value: 'Article' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '2015-06-26' + } + ] + } +}); +const rdItem = createSuccessfulRemoteDataObject(item); +const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) }); +const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); +mockObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkflowitem), id: '1234' }); + +describe('MyDSpaceReloadableActionsComponent', () => { + beforeEach(async(() => { + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null) + mockClaimedTaskDataService = new ClaimedTaskDataService(null, null, null, null, null, null, null, null); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + declarations: [PoolTaskActionsComponent], + providers: [ + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: PoolTaskDataService, useValue: mockDataService }, + { provide: ClaimedTaskDataService, useValue: mockClaimedTaskDataService }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(PoolTaskActionsComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PoolTaskActionsComponent); + component = fixture.componentInstance; + component.object = mockObject; + notificationsServiceStub = TestBed.get(NotificationsService); + router = TestBed.get(Router); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture = null; + component = null; + }); + + describe('on reload action init', () => { + + beforeEach(() => { + spyOn(component, 'initReloadAnchor').and.returnValue(null); + spyOn(component, 'initObjects'); + }); + + it('should call initReloadAnchor and initObjects on init', async(() => { + component.ngOnInit(); + + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(component.initReloadAnchor).toHaveBeenCalled(); + expect(component.initObjects).toHaveBeenCalled(); + }); + + })); + + }) + + describe('on action execution fail', () => { + + let remoteClaimTaskErrorResponse; + + beforeEach(() => { + + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + + const poolTaskHref = 'poolTaskHref'; + remoteClaimTaskErrorResponse = new ProcessTaskResponse(false, null, null); + const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); + + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskErrorResponse)); + spyOn(component, 'reloadObjectExecution').and.callThrough(); + spyOn(component.processCompleted, 'emit').and.callThrough(); + + (component as any).objectDataService = mockDataService; + }); + + it('should show error notification', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(notificationsServiceStub.error).toHaveBeenCalled(); + done(); + }) + }); + + it('should not call reloadObject', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.reloadObjectExecution).not.toHaveBeenCalled(); + done(); + }) + + }); + + it('should not emit processCompleted', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.processCompleted.emit).not.toHaveBeenCalled(); + done(); + }) + + }); + + }); + + describe('on action execution success', () => { + + beforeEach(() => { + + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + + const poolTaskHref = 'poolTaskHref'; + const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); + const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); + + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); + spyOn(component, 'reloadObjectExecution').and.callThrough(); + spyOn(component, 'convertReloadedObject').and.callThrough(); + spyOn(component.processCompleted, 'emit').and.callThrough(); + + (component as any).objectDataService = mockDataService; + }); + + it('should reloadObject in case of action execution success', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.reloadObjectExecution).toHaveBeenCalled(); + done(); + }) + }); + + it('should convert the reloaded object', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.convertReloadedObject).toHaveBeenCalled(); + done(); + }) + }); + + it('should emit the reloaded object in case of success', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.processCompleted.emit).toHaveBeenCalledWith({result: true, reloadedObject: result as any}); + done(); + }) + }); + + }); + + describe('on action execution success but without a reloadedObject', () => { + + beforeEach(() => { + + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + + const poolTaskHref = 'poolTaskHref'; + const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); + const remoteReloadedObjectResponse: any = createFailedRemoteDataObject(); + + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); + + spyOn(component, 'convertReloadedObject').and.returnValue(null); + spyOn(component, 'reload').and.returnValue(null); + + (component as any).objectDataService = mockDataService; + }); + + it('should call reload method', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.reload).toHaveBeenCalled(); + done(); + }) + }); + + }); + +}); diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts index 4fe3afd833..14928f156c 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -15,7 +15,7 @@ import { SearchService } from '../../core/shared/search/search.service'; import { Observable} from 'rxjs/internal/Observable'; import { of} from 'rxjs/internal/observable/of'; import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response'; -import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { getSearchResultFor } from '../search/search-result-element-decorator'; import { MyDSpaceActionsComponent } from './mydspace-actions'; @@ -128,9 +128,13 @@ export abstract class MyDSpaceReloadableActionsComponent { return this.reloadObjectExecution().pipe( switchMap((res) => { - return res instanceof RemoteData ? of(res).pipe(getFirstSucceededRemoteDataPayload()) : of(res) + if (res instanceof RemoteData) { + return of(res).pipe(getFirstCompletedRemoteData(), map((completed) => completed.payload)) + } else { + return of(res); + } })).pipe(map((dso) => { - return this.convertReloadedObject(dso); + return dso ? this.convertReloadedObject(dso) : dso; })) } diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts index 3b4eb23430..e8777d7a27 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts @@ -16,7 +16,7 @@ import { PoolTaskDataService } from '../../../core/tasks/pool-task-data.service' import { PoolTaskActionsComponent } from './pool-task-actions.component'; import { PoolTask } from '../../../core/tasks/models/pool-task-object.model'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; -import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../remote-data.utils'; +import { createSuccessfulRemoteDataObject } from '../../remote-data.utils'; import { getMockRequestService } from '../../mocks/request.service.mock'; import { RequestService } from '../../../core/data/request.service'; import { getMockSearchService } from '../../mocks/search-service.mock'; @@ -37,7 +37,7 @@ let router: RouterStub; const searchService = getMockSearchService(); -const requestServce = getMockRequestService(); +const requestService = getMockRequestService(); const item = Object.assign(new Item(), { bundles: observableOf({}), @@ -94,7 +94,7 @@ describe('PoolTaskActionsComponent', () => { { provide: PoolTaskDataService, useValue: mockDataService }, { provide: ClaimedTaskDataService, useValue: mockClaimedTaskDataService }, { provide: SearchService, useValue: searchService }, - { provide: RequestService, useValue: requestServce } + { provide: RequestService, useValue: requestService } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolTaskActionsComponent, { @@ -164,151 +164,4 @@ describe('PoolTaskActionsComponent', () => { })); - // general reload assertion - describe('on reload action init', () => { - - beforeEach(() => { - spyOn(component, 'initReloadAnchor').and.returnValue(null); - spyOn(component, 'initObjects'); - }); - - it('should call initReloadAnchor and initObjects on init', async(() => { - component.ngOnInit(); - - fixture.detectChanges(); - - fixture.whenStable().then(() => { - expect(component.initReloadAnchor).toHaveBeenCalled(); - expect(component.initObjects).toHaveBeenCalled(); - }); - - })); - - }) - - describe('on action execution fail', () => { - - let remoteClaimTaskErrorResponse; - - beforeEach(() => { - - mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); - - const poolTaskHref = 'poolTaskHref'; - remoteClaimTaskErrorResponse = new ProcessTaskResponse(false, null, null); - const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); - - spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); - spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); - spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskErrorResponse)); - spyOn(component, 'reloadObjectExecution').and.callThrough(); - spyOn(component.processCompleted, 'emit').and.callThrough(); - - (component as any).objectDataService = mockDataService; - }); - - it('should show error notification', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(notificationsServiceStub.error).toHaveBeenCalled(); - done(); - }) - }); - - it('should not call reloadObject', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(component.reloadObjectExecution).not.toHaveBeenCalled(); - done(); - }) - - }); - - it('should not emit processCompleted', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(component.processCompleted.emit).not.toHaveBeenCalled(); - done(); - }) - - }); - - }); - - describe('on action execution success', () => { - - beforeEach(() => { - - mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); - - const poolTaskHref = 'poolTaskHref'; - const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); - const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); - - spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); - spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); - spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); - spyOn(component, 'reloadObjectExecution').and.callThrough(); - spyOn(component, 'convertReloadedObject').and.callThrough(); - spyOn(component.processCompleted, 'emit').and.callThrough(); - - (component as any).objectDataService = mockDataService; - }); - - it('should reloadObject in case of action execution success', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(component.reloadObjectExecution).toHaveBeenCalled(); - done(); - }) - }); - - it('should convert the reloaded object', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(component.convertReloadedObject).toHaveBeenCalled(); - done(); - }) - }); - - it('should emit the reloaded object in case of success', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(component.processCompleted.emit).toHaveBeenCalledWith({result: true, reloadedObject: result as any}); - done(); - }) - }); - - }); - - describe('on action execution success but without a reloadedObject', () => { - - beforeEach(() => { - - mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); - - const poolTaskHref = 'poolTaskHref'; - const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); - const remoteReloadedObjectResponse: any = createFailedRemoteDataObject(); - - spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); - spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); - spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); - - spyOn(component, 'convertReloadedObject').and.returnValue(null); - spyOn(component, 'reload').and.returnValue(null); - - (component as any).objectDataService = mockDataService; - }); - - it('should call reload method', (done) => { - - component.startActionExecution().subscribe( (result) => { - expect(component.reload).toHaveBeenCalled(); - done(); - }) - }); - - }); - });