diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 1ee093e5c4..c24178a010 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -157,6 +157,9 @@ import { SequenceService } from './shared/sequence.service'; import { CoreState } from './core-state.model'; import { GroupDataService } from './eperson/group-data.service'; import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model'; +import { RatingAdvancedWorkflowInfo } from './tasks/models/rating-advanced-workflow-info.model'; +import { AdvancedWorkflowInfo } from './tasks/models/advanced-workflow-info.model'; +import { SelectReviewerAdvancedWorkflowInfo } from './tasks/models/select-reviewer-advanced-workflow-info.model'; import { AccessStatusObject } from '../shared/object-list/access-status-badge/access-status.model'; import { AccessStatusDataService } from './data/access-status-data.service'; import { LinkHeadService } from './services/link-head.service'; @@ -170,9 +173,6 @@ import { OrcidHistory } from './orcid/model/orcid-history.model'; import { OrcidAuthService } from './orcid/orcid-auth.service'; import { VocabularyDataService } from './submission/vocabularies/vocabulary.data.service'; import { VocabularyEntryDetailsDataService } from './submission/vocabularies/vocabulary-entry-details.data.service'; -import { RatingReviewerActionAdvancedInfo } from './tasks/models/rating-reviewer-action-advanced-info.model'; -import { ReviewerActionAdvancedInfo } from './tasks/models/reviewer-action-advanced-info.model'; -import { SelectReviewerActionAdvancedInfo } from './tasks/models/select-reviewer-action-advanced-info.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -341,9 +341,9 @@ export const models = Version, VersionHistory, WorkflowAction, - ReviewerActionAdvancedInfo, - RatingReviewerActionAdvancedInfo, - SelectReviewerActionAdvancedInfo, + AdvancedWorkflowInfo, + RatingAdvancedWorkflowInfo, + SelectReviewerAdvancedWorkflowInfo, TemplateItem, Feature, Authorization, diff --git a/src/app/core/tasks/models/advanced-workflow-info.model.ts b/src/app/core/tasks/models/advanced-workflow-info.model.ts new file mode 100644 index 0000000000..87991a375c --- /dev/null +++ b/src/app/core/tasks/models/advanced-workflow-info.model.ts @@ -0,0 +1,11 @@ +import { autoserialize } from 'cerialize'; + +/** + * An abstract model class for a {@link AdvancedWorkflowInfo} + */ +export abstract class AdvancedWorkflowInfo { + + @autoserialize + id: string; + +} diff --git a/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts b/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts new file mode 100644 index 0000000000..4e7793f875 --- /dev/null +++ b/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts @@ -0,0 +1,17 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for {@link RatingAdvancedWorkflowInfo} + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const RATING_ADVANCED_WORKFLOW_INFO = new ResourceType('ratingrevieweraction'); + +/** + * The resource type for {@link SelectReviewerAdvancedWorkflowInfo} + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO = new ResourceType('selectrevieweraction'); diff --git a/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts b/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts new file mode 100644 index 0000000000..b7861d4fe4 --- /dev/null +++ b/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts @@ -0,0 +1,28 @@ +import { typedObject } from '../../cache/builders/build-decorators'; +import { inheritSerialization, autoserialize } from 'cerialize'; +import { RATING_ADVANCED_WORKFLOW_INFO } from './advanced-workflow-info.resource-type'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; +import { ResourceType } from '../../shared/resource-type'; + +/** + * A model class for a {@link RatingAdvancedWorkflowInfo} + */ +@typedObject +@inheritSerialization(AdvancedWorkflowInfo) +export class RatingAdvancedWorkflowInfo extends AdvancedWorkflowInfo { + + static type: ResourceType = RATING_ADVANCED_WORKFLOW_INFO; + + /** + * Whether the description is required. + */ + @autoserialize + descriptionRequired: boolean; + + /** + * The maximum value. + */ + @autoserialize + maxValue: number; + +} diff --git a/src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts b/src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts deleted file mode 100644 index 2759d3edf2..0000000000 --- a/src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { typedObject } from '../../cache/builders/build-decorators'; -import { inheritSerialization, autoserialize } from 'cerialize'; -import { RATING_REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type'; -import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; - -/** - * A model class for a {@link RatingReviewerActionAdvancedInfo} - */ -@typedObject -@inheritSerialization(ReviewerActionAdvancedInfo) -export class RatingReviewerActionAdvancedInfo extends ReviewerActionAdvancedInfo { - - static type = RATING_REVIEWER_ACTION_ADVANCED_INFO; - - /** - * Whether the description is required. - */ - @autoserialize - descriptionRequired: boolean; - - /** - * The maximum value. - */ - @autoserialize - maxValue: number; - -} diff --git a/src/app/core/tasks/models/reviewer-action-advanced-info.model.ts b/src/app/core/tasks/models/reviewer-action-advanced-info.model.ts deleted file mode 100644 index dc423eec51..0000000000 --- a/src/app/core/tasks/models/reviewer-action-advanced-info.model.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { typedObject } from '../../cache/builders/build-decorators'; -import { autoserialize } from 'cerialize'; -import { REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type'; - -/** - * A model class for a {@link ReviewerActionAdvancedInfo} - */ -@typedObject -export class ReviewerActionAdvancedInfo { - - static type = REVIEWER_ACTION_ADVANCED_INFO; - - @autoserialize - id: string; - -} diff --git a/src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts b/src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts deleted file mode 100644 index 876f37495e..0000000000 --- a/src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ResourceType } from '../../shared/resource-type'; - -/** - * The resource type for {@link ReviewerActionAdvancedInfo} - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('revieweraction'); - -/** - * The resource type for {@link RatingReviewerActionAdvancedInfo} - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const RATING_REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('ratingrevieweraction'); - -/** - * The resource type for {@link SelectReviewerActionAdvancedInfo} - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const SELECT_REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('selectrevieweraction'); diff --git a/src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts b/src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts deleted file mode 100644 index cd8812f6af..0000000000 --- a/src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { typedObject } from '../../cache/builders/build-decorators'; -import { inheritSerialization, autoserialize } from 'cerialize'; -import { SELECT_REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type'; -import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; - -/** - * A model class for a {@link SelectReviewerActionAdvancedInfo} - */ -@typedObject -@inheritSerialization(ReviewerActionAdvancedInfo) -export class SelectReviewerActionAdvancedInfo extends ReviewerActionAdvancedInfo { - - static type = SELECT_REVIEWER_ACTION_ADVANCED_INFO; - - @autoserialize - group: string; - -} diff --git a/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts b/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts new file mode 100644 index 0000000000..b87770596e --- /dev/null +++ b/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts @@ -0,0 +1,19 @@ +import { typedObject } from '../../cache/builders/build-decorators'; +import { inheritSerialization, autoserialize } from 'cerialize'; +import { SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO } from './advanced-workflow-info.resource-type'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; +import { ResourceType } from '../../shared/resource-type'; + +/** + * A model class for a {@link SelectReviewerAdvancedWorkflowInfo} + */ +@typedObject +@inheritSerialization(AdvancedWorkflowInfo) +export class SelectReviewerAdvancedWorkflowInfo extends AdvancedWorkflowInfo { + + static type: ResourceType = SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO; + + @autoserialize + group: string; + +} diff --git a/src/app/core/tasks/models/workflow-action-object.model.ts b/src/app/core/tasks/models/workflow-action-object.model.ts index 6fa103bbea..0896e6b8f8 100644 --- a/src/app/core/tasks/models/workflow-action-object.model.ts +++ b/src/app/core/tasks/models/workflow-action-object.model.ts @@ -2,7 +2,7 @@ import { inheritSerialization, autoserialize } from 'cerialize'; import { typedObject } from '../../cache/builders/build-decorators'; import { DSpaceObject } from '../../shared/dspace-object.model'; import { WORKFLOW_ACTION } from './workflow-action-object.resource-type'; -import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; /** * A model class for a WorkflowAction @@ -40,6 +40,6 @@ export class WorkflowAction extends DSpaceObject { * The advanced info required by the advanced options */ @autoserialize - advancedInfo: ReviewerActionAdvancedInfo[]; + advancedInfo: AdvancedWorkflowInfo[]; } diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts index 3774b60f1a..d3f2ce4b6e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -7,9 +7,8 @@ import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/wo /** * Abstract component for rendering an advanced claimed task's action * To create a child-component for a new option: - * - Set the "option" of the component + * - Set the "option" and "workflowType" of the component * - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value - * - Optionally overwrite createBody if the request body requires more than just the option */ @Component({ selector: 'ds-advanced-claimed-task-action-abstract', @@ -17,7 +16,10 @@ import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/wo }) export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent implements OnInit { - workflowType: string; + /** + * The {@link WorkflowAction} id of the advanced workflow that needs to be opened. + */ + abstract workflowType: string; /** * Route to the workflow's task page @@ -40,6 +42,9 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime })); } + /** + * Navigates to the advanced workflow page based on the {@link workflow}. + */ openAdvancedClaimedTaskTab(): void { void this.router.navigate([this.workflowTaskPageRoute], { queryParams: { diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts index 2c2983c25c..8699dc702e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts @@ -9,11 +9,14 @@ import { } from '../abstract/advanced-claimed-task-actions-abstract.component'; import { ADVANCED_WORKFLOW_ACTION_RATING, - WORKFLOW_ADVANCED_TASK_OPTION_RATING, + ADVANCED_WORKFLOW_TASK_OPTION_RATING, } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; -@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_RATING) +/** + * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionRatingComponent} + */ +@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_RATING) @Component({ selector: 'ds-advanced-claimed-task-action-rating-reviewer', templateUrl: './advanced-claimed-task-action-rating.component.html', @@ -24,7 +27,7 @@ export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTas /** * This component represents the advanced select option */ - option = WORKFLOW_ADVANCED_TASK_OPTION_RATING; + option = ADVANCED_WORKFLOW_TASK_OPTION_RATING; workflowType = ADVANCED_WORKFLOW_ACTION_RATING; diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts index c7a2a2e545..7473c737d9 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -10,10 +10,13 @@ import { SearchService } from '../../../../core/shared/search/search.service'; import { RequestService } from '../../../../core/data/request.service'; import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, - WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; -@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER) +/** + * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent} + */ +@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER) @Component({ selector: 'ds-advanced-claimed-task-action-select-reviewer', templateUrl: './advanced-claimed-task-action-select-reviewer.component.html', @@ -24,7 +27,7 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl /** * This component represents the advanced select option */ - option = WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER; + option = ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER; workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts index f2511f0490..cbb85b6ad8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action-page.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; describe('AdvancedWorkflowActionPageComponent', () => { @@ -20,9 +20,9 @@ describe('AdvancedWorkflowActionPageComponent', () => { provide: ActivatedRoute, useValue: { snapshot: { - queryParams: convertToParamMap({ + queryParams: { workflow: 'testaction', - }), + }, }, }, }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts index e6c7da7d59..91dce19a5e 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts @@ -1,6 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +/** + * The Advanced Workflow page containing the correct {@link AdvancedWorkflowActionComponent} + * based on the route parameters. + */ @Component({ selector: 'ds-advanced-workflow-action-page', templateUrl: './advanced-workflow-action-page.component.html', diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts index a75d4e30a2..4abbe1a0c1 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionRatingComponent } from './advanced-workflow-action-rating.component'; -import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; +import { + AdvancedWorkflowActionRatingComponent, + ADVANCED_WORKFLOW_TASK_OPTION_RATING +} from './advanced-workflow-action-rating.component'; +import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -17,10 +20,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { VarDirective } from '../../../shared/utils/var.directive'; import { RatingModule } from 'ngx-bootstrap/rating'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; +import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model'; +const claimedTaskId = '2'; const workflowId = '1'; describe('AdvancedWorkflowActionRatingComponent', () => { + const workflowItem: WorkflowItem = new WorkflowItem(); + workflowItem.item = createSuccessfulRemoteDataObject$(new Item()); let component: AdvancedWorkflowActionRatingComponent; let fixture: ComponentFixture; @@ -52,11 +64,13 @@ describe('AdvancedWorkflowActionRatingComponent', () => { useValue: { data: observableOf({ id: workflowId, + wfi: createSuccessfulRemoteDataObject(workflowItem), }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { + claimedTask: claimedTaskId, workflow: 'testaction', - }), + }, }, }, }, @@ -67,6 +81,7 @@ describe('AdvancedWorkflowActionRatingComponent', () => { { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); @@ -80,7 +95,96 @@ describe('AdvancedWorkflowActionRatingComponent', () => { fixture.debugElement.nativeElement.remove(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('performAction', () => { + let ratingAdvancedWorkflowInfo: RatingAdvancedWorkflowInfo; + beforeEach(() => { + ratingAdvancedWorkflowInfo = new RatingAdvancedWorkflowInfo(); + ratingAdvancedWorkflowInfo.maxValue = 5; + spyOn(component, 'getAdvancedInfo').and.returnValue(ratingAdvancedWorkflowInfo); + spyOn(component, 'previousPage'); + // The form validators are set in the HTML code so the getAdvancedInfo needs to return a value + fixture.detectChanges(); + }); + + describe('with required review', () => { + beforeEach(() => { + ratingAdvancedWorkflowInfo.descriptionRequired = true; + fixture.detectChanges(); + }); + + it('should call the claimedTaskDataService with the rating and the required description when it has been rated and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: 'Good job!', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + review: 'Good job!', + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should not call the claimedTaskDataService when the required description is empty', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: '', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled(); + expect(notificationService.success).not.toHaveBeenCalled(); + expect(component.previousPage).not.toHaveBeenCalled(); + }); + }); + + describe('with an optional review', () => { + beforeEach(() => { + ratingAdvancedWorkflowInfo.descriptionRequired = false; + fixture.detectChanges(); + }); + + it('should call the claimedTaskDataService with the optional review when provided and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: 'Good job!', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + review: 'Good job!', + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should call the claimedTaskDataService when the optional description is empty and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: '', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + }); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts index eb06e49243..f98e83f8be 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts @@ -6,12 +6,15 @@ import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/adv import { FormGroup, FormControl } from '@angular/forms'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { - RatingReviewerActionAdvancedInfo -} from '../../../core/tasks/models/rating-reviewer-action-advanced-info.model'; + RatingAdvancedWorkflowInfo +} from '../../../core/tasks/models/rating-advanced-workflow-info.model'; -export const WORKFLOW_ADVANCED_TASK_OPTION_RATING = 'submit_score'; +export const ADVANCED_WORKFLOW_TASK_OPTION_RATING = 'submit_score'; export const ADVANCED_WORKFLOW_ACTION_RATING = 'scorereviewaction'; +/** + * The page on which reviewers can rate submitted items. + */ @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING) @Component({ selector: 'ds-advanced-workflow-action-rating-reviewer', @@ -23,7 +26,7 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio ratingForm: FormGroup; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.ratingForm = new FormGroup({ review: new FormControl(''), @@ -43,9 +46,12 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio } } + /** + * Returns the task option, the score and the review if one was provided + */ createBody(): any { const body = { - [WORKFLOW_ADVANCED_TASK_OPTION_RATING]: true, + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, score: this.ratingForm.get('rating').value, }; if (this.ratingForm.get('review').value !== '') { @@ -59,8 +65,8 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio return ADVANCED_WORKFLOW_ACTION_RATING; } - getAdvancedInfo(workflowAction: WorkflowAction | null): RatingReviewerActionAdvancedInfo | null { - return workflowAction ? (workflowAction.advancedInfo[0] as RatingReviewerActionAdvancedInfo) : null; + getAdvancedInfo(workflowAction: WorkflowAction | null): RatingAdvancedWorkflowInfo | null { + return workflowAction ? (workflowAction.advancedInfo[0] as RatingAdvancedWorkflowInfo) : null; } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index a3ede08b1d..855bde79a7 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionSelectReviewerComponent } from './advanced-workflow-action-select-reviewer.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { + AdvancedWorkflowActionSelectReviewerComponent, + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER +} from './advanced-workflow-action-select-reviewer.component'; +import { ActivatedRoute } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; import { RouterTestingModule } from '@angular/router/testing'; @@ -14,10 +17,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; import { of as observableOf } from 'rxjs'; +import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; +import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +const claimedTaskId = '2'; const workflowId = '1'; describe('AdvancedWorkflowActionSelectReviewerComponent', () => { + const workflowItem: WorkflowItem = new WorkflowItem(); + workflowItem.item = createSuccessfulRemoteDataObject$(new Item()); let component: AdvancedWorkflowActionSelectReviewerComponent; let fixture: ComponentFixture; @@ -46,11 +58,13 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { useValue: { data: observableOf({ id: workflowId, + wfi: createSuccessfulRemoteDataObject(workflowItem), }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { + claimedTask: claimedTaskId, workflow: 'testaction', - }), + }, }, }, }, @@ -60,6 +74,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); @@ -67,9 +82,49 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent); component = fixture.componentInstance; fixture.detectChanges(); + spyOn(component, 'previousPage'); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + describe('performAction', () => { + it('should call the claimedTaskDataService with the list of selected ePersons', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.selectedReviewers = [EPersonMock, EPersonMock2]; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: [EPersonMock.id, EPersonMock2.id], + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should not call the claimedTaskDataService with the list of selected ePersons when it\'s empty', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.selectedReviewers = []; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled(); + }); + + it('should not call the return to mydspace page when the request failed', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(false))); + component.selectedReviewers = [EPersonMock, EPersonMock2]; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: [EPersonMock.id, EPersonMock2.id], + }); + expect(notificationService.error).toHaveBeenCalled(); + expect(component.previousPage).not.toHaveBeenCalled(); + }); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index f766c97f39..3d07acdebe 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -5,17 +5,20 @@ import { import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { - SelectReviewerActionAdvancedInfo -} from '../../../core/tasks/models/select-reviewer-action-advanced-info.model'; + SelectReviewerAdvancedWorkflowInfo +} from '../../../core/tasks/models/select-reviewer-advanced-workflow-info.model'; import { EPersonListActionConfig } from '../../../access-control/group-registry/group-form/members-list/members-list.component'; import { Subscription } from 'rxjs'; import { EPerson } from '../../../core/eperson/models/eperson.model'; -export const WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; +export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; +/** + * The page on which Review Managers can assign Reviewers to review an item. + */ @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER) @Component({ selector: 'ds-advanced-workflow-action-select-reviewer', @@ -75,7 +78,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf } this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => { if (workflowAction) { - this.groupId = (workflowAction.advancedInfo as SelectReviewerActionAdvancedInfo[])[0].group; + this.groupId = (workflowAction.advancedInfo as SelectReviewerAdvancedWorkflowInfo[])[0].group; } else { this.groupId = null; } @@ -86,18 +89,23 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; } + /** + * Only performs the action when some reviewers have been selected. + */ performAction(): void { if (this.selectedReviewers.length > 0) { super.performAction(); } else { this.displayError = true; } - console.log(this.displayError); } + /** + * Returns the task option and the selected {@link EPerson} id(s) + */ createBody(): any { return { - [WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER]: true, + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id), }; } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts index 7576331af6..7c8db782ce 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA, SimpleChange, DebugElement } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -31,6 +31,7 @@ import { NotificationsServiceStub } from '../../../../shared/testing/notificatio import { RouterMock } from '../../../../shared/mocks/router.mock'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; describe('ReviewersListComponent', () => { let component: ReviewersListComponent; @@ -45,6 +46,8 @@ describe('ReviewersListComponent', () => { let epersonMembers; let subgroupMembers; let paginationService; + let ePersonDtoModel1: EpersonDtoModel; + let ePersonDtoModel2: EpersonDtoModel; beforeEach(waitForAsync(() => { activeGroup = GroupMock; @@ -119,7 +122,6 @@ describe('ReviewersListComponent', () => { findById(id: string) { for (const group of allGroups) { if (group.id === id) { - console.log('found', group); return createSuccessfulRemoteDataObject$(group); } } @@ -167,9 +169,12 @@ describe('ReviewersListComponent', () => { fixture.debugElement.nativeElement.remove(); })); - it('should create ReviewersListComponent', inject([ReviewersListComponent], (comp: ReviewersListComponent) => { - expect(comp).toBeDefined(); - })); + beforeEach(() => { + ePersonDtoModel1 = new EpersonDtoModel(); + ePersonDtoModel1.eperson = EPersonMock; + ePersonDtoModel2 = new EpersonDtoModel(); + ePersonDtoModel2.eperson = EPersonMock2; + }); describe('when no group is selected', () => { beforeEach(() => { @@ -179,18 +184,18 @@ describe('ReviewersListComponent', () => { fixture.detectChanges(); }); - it('should show no epersons because no group is selected', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(0); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + it('should show no ePersons because no group is selected', () => { + const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(ePersonIdsFound.length).toEqual(0); + epersonMembers.map((ePerson: EPerson) => { + expect(ePersonIdsFound.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === ePerson.uuid); })).not.toBeTruthy(); }); }); }); - describe('when group is selected', () => { + describe('when a group is selected', () => { beforeEach(() => { component.ngOnChanges({ groupId: new SimpleChange(undefined, GroupMock.id, true) @@ -198,15 +203,50 @@ describe('ReviewersListComponent', () => { fixture.detectChanges(); }); - it('should show all eperson members of group', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(1); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + it('should show all ePerson members of group', () => { + const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(ePersonIdsFound.length).toEqual(1); + epersonMembers.map((ePerson: EPerson) => { + expect(ePersonIdsFound.find((foundEl: DebugElement) => { + return (foundEl.nativeElement.textContent.trim() === ePerson.uuid); })).toBeTruthy(); }); }); }); + + it('should replace the value when a new member is added when multipleReviewers is false', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + component.multipleReviewers = false; + component.selectedReviewers = [ePersonDtoModel1]; + + component.addMemberToGroup(ePersonDtoModel2); + + expect(component.selectedReviewers).toEqual([ePersonDtoModel2]); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel2.eperson]); + }); + + it('should add the value when a new member is added when multipleReviewers is true', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + component.multipleReviewers = true; + component.selectedReviewers = [ePersonDtoModel1]; + + component.addMemberToGroup(ePersonDtoModel2); + + expect(component.selectedReviewers).toEqual([ePersonDtoModel1, ePersonDtoModel2]); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel1.eperson, ePersonDtoModel2.eperson]); + }); + + it('should delete the member when present', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + ePersonDtoModel1.memberOfGroup = true; + component.selectedReviewers = [ePersonDtoModel1]; + + component.deleteMemberFromGroup(ePersonDtoModel1); + + expect(component.selectedReviewers).toEqual([]); + expect(ePersonDtoModel1.memberOfGroup).toBeFalse(); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]); + }); + }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts index 0cd485e638..7112a30543 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts @@ -14,7 +14,8 @@ import { Observable, of as observableOf } from 'rxjs'; import { hasValue } from '../../../../shared/empty.util'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { - MembersListComponent, EPersonListActionConfig + MembersListComponent, + EPersonListActionConfig, } from '../../../../access-control/group-registry/group-form/members-list/members-list.component'; /** @@ -26,6 +27,9 @@ enum SubKey { SearchResultsDTO, } +/** + * A custom {@link MembersListComponent} for the advanced SelectReviewer workflow. + */ @Component({ selector: 'ds-reviewers-list', // templateUrl: './reviewers-list.component.html', @@ -83,6 +87,12 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn } } + /** + * Sets the list of currently selected members, when no group is defined the list of {@link selectedReviewers} + * will be set. + * + * @param page The number of the page to retrieve + */ retrieveMembers(page: number): void { this.config.currentPage = page; if (this.groupId === null) { @@ -95,19 +105,35 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn } } + /** + * Checks whether the given {@link possibleMember} is part of the {@link selectedReviewers}. + * + * @param possibleMember The {@link EPerson} that needs to be checked + */ isMemberOfGroup(possibleMember: EPerson): Observable { return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id))); } + /** + * Removes the {@link ePerson} from the {@link selectedReviewers} + * + * @param ePerson The {@link EpersonDtoModel} containg the {@link EPerson} to remove + */ deleteMemberFromGroup(ePerson: EpersonDtoModel) { ePerson.memberOfGroup = false; const index = this.selectedReviewers.indexOf(ePerson); if (index !== -1) { this.selectedReviewers.splice(index, 1); } - this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson)); + this.selectedReviewersUpdated.emit(this.selectedReviewers.map((ePersonDtoModel: EpersonDtoModel) => ePersonDtoModel.eperson)); } + /** + * Adds the {@link ePerson} to the {@link selectedReviewers} (or replaces it when {@link multipleReviewers} is + * `false`). Afterwards it will emit the list. + * + * @param ePerson The {@link EPerson} to add to the list + */ addMemberToGroup(ePerson: EpersonDtoModel) { ePerson.memberOfGroup = true; if (!this.multipleReviewers) { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts index b836a8704a..597abe4949 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts @@ -5,7 +5,7 @@ import { MockComponent } from 'ng-mocks'; import { DSOSelectorComponent } from '../../../shared/dso-selector/dso-selector/dso-selector.component'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { RouterTestingModule } from '@angular/router/testing'; @@ -54,9 +54,9 @@ describe('AdvancedWorkflowActionComponent', () => { id: workflowId, }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { workflow: 'testaction', - }), + }, }, }, }, @@ -108,7 +108,8 @@ describe('AdvancedWorkflowActionComponent', () => { }); @Component({ - selector: 'ds-test-cmp', + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', template: '' }) class TestComponent extends AdvancedWorkflowActionComponent { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts index 256a4d8915..982e5f8eac 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts @@ -13,6 +13,12 @@ import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.se import { map } from 'rxjs/operators'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +/** + * Abstract component for rendering an advanced claimed task's workflow page + * To create a child-component for a new option: + * - Set the "getType()" of the component + * - Implement the createBody, should always contain at least the ADVANCED_WORKFLOW_TASK_OPTION + */ @Component({ selector: 'ds-advanced-workflow-action', template: '', @@ -62,7 +68,7 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction /** * Submits the task with the given {@link createBody}. * - * @param id + * @param id The task id */ sendRequest(id: string): Observable { return this.claimedTaskDataService.submitTask(id, this.createBody()).pipe( diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html index 2374ed7913..0904d0fcde 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html @@ -1 +1 @@ - + diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts index 321a6b954d..2c12b07589 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts @@ -2,6 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-actions-loader.component'; import { Router } from '@angular/router'; import { RouterStub } from '../../../shared/testing/router.stub'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; +import { + rendersAdvancedWorkflowTaskOption +} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; +import { By } from '@angular/platform-browser'; +import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; + +const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction'; describe('AdvancedWorkflowActionsLoaderComponent', () => { let component: AdvancedWorkflowActionsLoaderComponent; @@ -14,21 +23,61 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { await TestBed.configureTestingModule({ declarations: [ + AdvancedWorkflowActionsDirective, AdvancedWorkflowActionsLoaderComponent, ], providers: [ { provide: Router, useValue: router }, ], + }).overrideComponent(AdvancedWorkflowActionsLoaderComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + entryComponents: [AdvancedWorkflowActionTestComponent], + }, }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(AdvancedWorkflowActionsLoaderComponent); component = fixture.componentInstance; + component.type = ADVANCED_WORKFLOW_ACTION_TEST; fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + describe('When the component is rendered', () => { + it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => { + spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST); + expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => { + spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined); + component.type = 'nonexistingaction'; + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction'); + expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]); + }); }); }); + +@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_TEST) +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', + template: '', +}) +class AdvancedWorkflowActionTestComponent { +} diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index ac6d2f171d..32f14c015d 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -3,10 +3,13 @@ import { hasValue } from '../../../shared/empty.util'; import { getAdvancedComponentByWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; -import { AdvancedClaimedTaskActionsDirective } from './advanced-claimed-task-actions.directive'; +import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; import { Router } from '@angular/router'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; +/** + * Component for loading a {@link AdvancedWorkflowActionComponent} depending on the "{@link type}" input + */ @Component({ selector: 'ds-advanced-workflow-actions-loader', templateUrl: './advanced-workflow-actions-loader.component.html', @@ -23,7 +26,7 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { /** * Directive to determine where the dynamic child component is located */ - @ViewChild(AdvancedClaimedTaskActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedClaimedTaskActionsDirective; + @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective; constructor( private componentFactoryResolver: ComponentFactoryResolver, @@ -47,8 +50,8 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { } } - getComponentByWorkflowTaskOption(option: string) { - return getAdvancedComponentByWorkflowTaskOption(option); + getComponentByWorkflowTaskOption(type: string): any { + return getAdvancedComponentByWorkflowTaskOption(type); } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts similarity index 73% rename from src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive.ts rename to src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts index b2ed49b502..e569f6cc6f 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts @@ -1,12 +1,12 @@ import { Directive, ViewContainerRef } from '@angular/core'; @Directive({ - selector: '[dsAdvancedClaimedTaskActions]', + selector: '[dsAdvancedWorkflowActions]', }) /** * Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component */ -export class AdvancedClaimedTaskActionsDirective { +export class AdvancedWorkflowActionsDirective { constructor( public viewContainerRef: ViewContainerRef, diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts index dc60bd7d2b..aaf75fbb9f 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -24,8 +24,8 @@ import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; import { - AdvancedClaimedTaskActionsDirective -} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive'; + AdvancedWorkflowActionsDirective +} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive'; import { AccessControlModule } from '../access-control/access-control.module'; import { ReviewersListComponent @@ -54,7 +54,7 @@ import { RatingModule } from 'ngx-bootstrap/rating'; AdvancedWorkflowActionRatingComponent, AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionPageComponent, - AdvancedClaimedTaskActionsDirective, + AdvancedWorkflowActionsDirective, ReviewersListComponent, ] })