diff --git a/src/app/core/json-patch/json-patch-operations.service.spec.ts b/src/app/core/json-patch/json-patch-operations.service.spec.ts index 5055fabbd1..1107d27e56 100644 --- a/src/app/core/json-patch/json-patch-operations.service.spec.ts +++ b/src/app/core/json-patch/json-patch-operations.service.spec.ts @@ -1,9 +1,8 @@ -import { getTestScheduler } from 'jasmine-marbles'; +import { getTestScheduler, hot } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { of as observableOf } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { Store } from '@ngrx/store'; - import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { RequestService } from '../data/request.service'; import { SubmissionPatchRequest } from '../data/request.models'; @@ -22,6 +21,7 @@ import { } from './json-patch-operations.actions'; import { RequestEntry } from '../data/request.reducer'; import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { _deepClone } from 'fast-json-patch/lib/helpers'; class TestService extends JsonPatchOperationsService { protected linkPath = ''; @@ -196,6 +196,32 @@ describe('JsonPatchOperationsService test suite', () => { }); }); + describe('hasPendingOperations', () => { + + it('should return true when there are pending operations', () => { + + const expected = hot('(x|)', { x: true }); + + const result = service.hasPendingOperations(testJsonPatchResourceType); + expect(result).toBeObservable(expected); + + }); + + it('should return false when there are not pending operations', () => { + + const mockStateNoOp = _deepClone(mockState); + mockStateNoOp['json/patch'][testJsonPatchResourceType].children = []; + store.select.and.returnValue(observableOf(mockStateNoOp['json/patch'][testJsonPatchResourceType])); + + const expected = hot('(x|)', { x: false }); + + const result = service.hasPendingOperations(testJsonPatchResourceType); + expect(result).toBeObservable(expected); + + }); + + }); + describe('jsonPatchByResourceID', () => { it('should call submitJsonPatchOperations method', () => { diff --git a/src/app/core/json-patch/json-patch-operations.service.ts b/src/app/core/json-patch/json-patch-operations.service.ts index 6646e67862..5cf3d503a6 100644 --- a/src/app/core/json-patch/json-patch-operations.service.ts +++ b/src/app/core/json-patch/json-patch-operations.service.ts @@ -161,6 +161,18 @@ export abstract class JsonPatchOperationsService { + return this.store.select(jsonPatchOperationsByResourceType(resourceType)).pipe( + map((val) => !isEmpty(val) && Object.values(val.children) + .filter((section) => !isEmpty((section as any).body)).length > 0), + distinctUntilChanged(), + ); + } + /** * Make a new JSON Patch request with all operations related to the specified resource id * diff --git a/src/app/shared/testing/submission-service.stub.ts b/src/app/shared/testing/submission-service.stub.ts index 35c3ddfee0..93192371c6 100644 --- a/src/app/shared/testing/submission-service.stub.ts +++ b/src/app/shared/testing/submission-service.stub.ts @@ -20,6 +20,7 @@ export class SubmissionServiceStub { getSubmissionStatus = jasmine.createSpy('getSubmissionStatus'); getSubmissionSaveProcessingStatus = jasmine.createSpy('getSubmissionSaveProcessingStatus'); getSubmissionDepositProcessingStatus = jasmine.createSpy('getSubmissionDepositProcessingStatus'); + hasNotSavedModification = jasmine.createSpy('hasNotSavedModification'); isSectionHidden = jasmine.createSpy('isSectionHidden'); isSubmissionLoading = jasmine.createSpy('isSubmissionLoading'); notifyNewSection = jasmine.createSpy('notifyNewSection'); diff --git a/src/app/submission/form/footer/submission-form-footer.component.html b/src/app/submission/form/footer/submission-form-footer.component.html index 938c81a33f..29bfff2660 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.html +++ b/src/app/submission/form/footer/submission-form-footer.component.html @@ -12,7 +12,7 @@ diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts index c8860e3541..aa5e7f996b 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts @@ -224,6 +224,22 @@ describe('SubmissionFormFooterComponent Component', () => { expect(depositBtn.nativeElement.disabled).toBeFalsy(); }); + it('should disable save button when all modifications had been saved', () => { + comp.hasNotSavedModification = observableOf(false); + fixture.detectChanges(); + + const saveBtn: any = fixture.debugElement.query(By.css('#save')); + expect(saveBtn.nativeElement.disabled).toBeTruthy(); + }); + + it('should enable save button when there are not saved modifications', () => { + comp.hasNotSavedModification = observableOf(true); + fixture.detectChanges(); + + const saveBtn: any = fixture.debugElement.query(By.css('#save')); + expect(saveBtn.nativeElement.disabled).toBeFalsy(); + }); + }); }); diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 0636c3f6d3..be1faf57ec 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -49,6 +49,11 @@ export class SubmissionFormFooterComponent implements OnChanges { */ public submissionIsInvalid: Observable = observableOf(true); + /** + * A boolean representing if submission form has unsaved modifications + */ + public hasNotSavedModification: Observable; + /** * Initialize instance variables * @@ -73,6 +78,7 @@ export class SubmissionFormFooterComponent implements OnChanges { this.processingSaveStatus = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId); this.processingDepositStatus = this.submissionService.getSubmissionDepositProcessingStatus(this.submissionId); this.showDepositAndDiscard = observableOf(this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkspaceItem); + this.hasNotSavedModification = this.submissionService.hasNotSavedModification(); } } diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index 6455638eef..f601239387 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -46,6 +46,8 @@ import { SearchService } from '../core/shared/search/search.service'; import { Item } from '../core/shared/item.model'; import { storeModuleConfig } from '../app.reducer'; import { environment } from '../../environments/environment'; +import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service'; +import { SubmissionJsonPatchOperationsServiceStub } from '../shared/testing/submission-json-patch-operations-service.stub'; describe('SubmissionService test suite', () => { const collectionId = '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f'; @@ -345,6 +347,7 @@ describe('SubmissionService test suite', () => { const router = new RouterMock(); const selfUrl = 'https://rest.api/dspace-spring-rest/api/submission/workspaceitems/826'; const submissionDefinition: any = mockSubmissionDefinition; + const submissionJsonPatchOperationsService = new SubmissionJsonPatchOperationsServiceStub(); let scheduler: TestScheduler; let service: SubmissionService; @@ -371,6 +374,7 @@ describe('SubmissionService test suite', () => { { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, { provide: SearchService, useValue: searchService }, { provide: RequestService, useValue: requestServce }, + { provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsService }, NotificationsService, RouteService, SubmissionService, @@ -753,6 +757,20 @@ describe('SubmissionService test suite', () => { }); }); + describe('hasNotSavedModification', () => { + it('should call jsonPatchOperationService hasPendingOperation observable', () => { + (service as any).jsonPatchOperationService.hasPendingOperations = jasmine.createSpy('hasPendingOperations') + .and.returnValue(observableOf(true)); + + scheduler = getTestScheduler(); + scheduler.schedule(() => service.hasNotSavedModification()); + scheduler.flush(); + + expect((service as any).jsonPatchOperationService.hasPendingOperations).toHaveBeenCalledWith('sections'); + + }); + }); + describe('isSectionHidden', () => { it('should return true/false when section is hidden/visible', () => { let section: any = { diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 0277ac6e5a..f7bfa3fbed 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -45,6 +45,7 @@ import { RequestService } from '../core/data/request.service'; import { SearchService } from '../core/shared/search/search.service'; import { Item } from '../core/shared/item.model'; import { environment } from '../../environments/environment'; +import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service'; /** * A service that provides methods used in submission process. @@ -82,7 +83,8 @@ export class SubmissionService { protected store: Store, protected translate: TranslateService, protected searchService: SearchService, - protected requestService: RequestService) { + protected requestService: RequestService, + protected jsonPatchOperationService: SubmissionJsonPatchOperationsService) { } /** @@ -429,6 +431,16 @@ export class SubmissionService { startWith(false)); } + /** + * Return whether submission unsaved modification are present + * + * @return Observable + * observable with submission unsaved modification presence + */ + hasNotSavedModification(): Observable { + return this.jsonPatchOperationService.hasPendingOperations('sections'); + } + /** * Return the visibility status of the specified section *