[835] Auto-save in new Item Submission form breaks the form

Submission form Save button disabled when no pending operations are present
This commit is contained in:
Alessandro Martelli
2020-11-27 16:25:41 +01:00
parent d47f686b95
commit de372896e7
8 changed files with 95 additions and 4 deletions

View File

@@ -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<SubmitDataResponseDefinitionObject, SubmissionPatchRequest> {
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', () => {

View File

@@ -161,6 +161,18 @@ export abstract class JsonPatchOperationsService<ResponseDefinitionDomain, Patch
return this.submitJsonPatchOperations(href$, resourceType);
}
/**
* Select the jsonPatch operation related to the specified resource type.
* @param resourceType
*/
public hasPendingOperations(resourceType: string): Observable<boolean> {
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
*

View File

@@ -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');

View File

@@ -12,7 +12,7 @@
<button type="button"
class="btn btn-info"
id="save"
[disabled]="(processingSaveStatus | async)"
[disabled]="(processingSaveStatus | async) || !(hasNotSavedModification | async)"
(click)="save($event)">
<span>{{'submission.general.save' | translate}}</span>
</button>

View File

@@ -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();
});
});
});

View File

@@ -49,6 +49,11 @@ export class SubmissionFormFooterComponent implements OnChanges {
*/
public submissionIsInvalid: Observable<boolean> = observableOf(true);
/**
* A boolean representing if submission form has unsaved modifications
*/
public hasNotSavedModification: Observable<boolean>;
/**
* 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();
}
}

View File

@@ -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 = {

View File

@@ -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<SubmissionState>,
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<boolean>
* observable with submission unsaved modification presence
*/
hasNotSavedModification(): Observable<boolean> {
return this.jsonPatchOperationService.hasPendingOperations('sections');
}
/**
* Return the visibility status of the specified section
*