mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 02:24:11 +00:00
[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:
@@ -1,9 +1,8 @@
|
|||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { SubmissionPatchRequest } from '../data/request.models';
|
import { SubmissionPatchRequest } from '../data/request.models';
|
||||||
@@ -22,6 +21,7 @@ import {
|
|||||||
} from './json-patch-operations.actions';
|
} from './json-patch-operations.actions';
|
||||||
import { RequestEntry } from '../data/request.reducer';
|
import { RequestEntry } from '../data/request.reducer';
|
||||||
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { _deepClone } from 'fast-json-patch/lib/helpers';
|
||||||
|
|
||||||
class TestService extends JsonPatchOperationsService<SubmitDataResponseDefinitionObject, SubmissionPatchRequest> {
|
class TestService extends JsonPatchOperationsService<SubmitDataResponseDefinitionObject, SubmissionPatchRequest> {
|
||||||
protected linkPath = '';
|
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', () => {
|
describe('jsonPatchByResourceID', () => {
|
||||||
|
|
||||||
it('should call submitJsonPatchOperations method', () => {
|
it('should call submitJsonPatchOperations method', () => {
|
||||||
|
@@ -161,6 +161,18 @@ export abstract class JsonPatchOperationsService<ResponseDefinitionDomain, Patch
|
|||||||
return this.submitJsonPatchOperations(href$, resourceType);
|
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
|
* Make a new JSON Patch request with all operations related to the specified resource id
|
||||||
*
|
*
|
||||||
|
@@ -20,6 +20,7 @@ export class SubmissionServiceStub {
|
|||||||
getSubmissionStatus = jasmine.createSpy('getSubmissionStatus');
|
getSubmissionStatus = jasmine.createSpy('getSubmissionStatus');
|
||||||
getSubmissionSaveProcessingStatus = jasmine.createSpy('getSubmissionSaveProcessingStatus');
|
getSubmissionSaveProcessingStatus = jasmine.createSpy('getSubmissionSaveProcessingStatus');
|
||||||
getSubmissionDepositProcessingStatus = jasmine.createSpy('getSubmissionDepositProcessingStatus');
|
getSubmissionDepositProcessingStatus = jasmine.createSpy('getSubmissionDepositProcessingStatus');
|
||||||
|
hasNotSavedModification = jasmine.createSpy('hasNotSavedModification');
|
||||||
isSectionHidden = jasmine.createSpy('isSectionHidden');
|
isSectionHidden = jasmine.createSpy('isSectionHidden');
|
||||||
isSubmissionLoading = jasmine.createSpy('isSubmissionLoading');
|
isSubmissionLoading = jasmine.createSpy('isSubmissionLoading');
|
||||||
notifyNewSection = jasmine.createSpy('notifyNewSection');
|
notifyNewSection = jasmine.createSpy('notifyNewSection');
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-info"
|
class="btn btn-info"
|
||||||
id="save"
|
id="save"
|
||||||
[disabled]="(processingSaveStatus | async)"
|
[disabled]="(processingSaveStatus | async) || !(hasNotSavedModification | async)"
|
||||||
(click)="save($event)">
|
(click)="save($event)">
|
||||||
<span>{{'submission.general.save' | translate}}</span>
|
<span>{{'submission.general.save' | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -224,6 +224,22 @@ describe('SubmissionFormFooterComponent Component', () => {
|
|||||||
expect(depositBtn.nativeElement.disabled).toBeFalsy();
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -49,6 +49,11 @@ export class SubmissionFormFooterComponent implements OnChanges {
|
|||||||
*/
|
*/
|
||||||
public submissionIsInvalid: Observable<boolean> = observableOf(true);
|
public submissionIsInvalid: Observable<boolean> = observableOf(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if submission form has unsaved modifications
|
||||||
|
*/
|
||||||
|
public hasNotSavedModification: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
*
|
*
|
||||||
@@ -73,6 +78,7 @@ export class SubmissionFormFooterComponent implements OnChanges {
|
|||||||
this.processingSaveStatus = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
|
this.processingSaveStatus = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
|
||||||
this.processingDepositStatus = this.submissionService.getSubmissionDepositProcessingStatus(this.submissionId);
|
this.processingDepositStatus = this.submissionService.getSubmissionDepositProcessingStatus(this.submissionId);
|
||||||
this.showDepositAndDiscard = observableOf(this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkspaceItem);
|
this.showDepositAndDiscard = observableOf(this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkspaceItem);
|
||||||
|
this.hasNotSavedModification = this.submissionService.hasNotSavedModification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -46,6 +46,8 @@ import { SearchService } from '../core/shared/search/search.service';
|
|||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
import { storeModuleConfig } from '../app.reducer';
|
import { storeModuleConfig } from '../app.reducer';
|
||||||
import { environment } from '../../environments/environment';
|
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', () => {
|
describe('SubmissionService test suite', () => {
|
||||||
const collectionId = '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f';
|
const collectionId = '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f';
|
||||||
@@ -345,6 +347,7 @@ describe('SubmissionService test suite', () => {
|
|||||||
const router = new RouterMock();
|
const router = new RouterMock();
|
||||||
const selfUrl = 'https://rest.api/dspace-spring-rest/api/submission/workspaceitems/826';
|
const selfUrl = 'https://rest.api/dspace-spring-rest/api/submission/workspaceitems/826';
|
||||||
const submissionDefinition: any = mockSubmissionDefinition;
|
const submissionDefinition: any = mockSubmissionDefinition;
|
||||||
|
const submissionJsonPatchOperationsService = new SubmissionJsonPatchOperationsServiceStub();
|
||||||
|
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
let service: SubmissionService;
|
let service: SubmissionService;
|
||||||
@@ -371,6 +374,7 @@ describe('SubmissionService test suite', () => {
|
|||||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||||
{ provide: SearchService, useValue: searchService },
|
{ provide: SearchService, useValue: searchService },
|
||||||
{ provide: RequestService, useValue: requestServce },
|
{ provide: RequestService, useValue: requestServce },
|
||||||
|
{ provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsService },
|
||||||
NotificationsService,
|
NotificationsService,
|
||||||
RouteService,
|
RouteService,
|
||||||
SubmissionService,
|
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', () => {
|
describe('isSectionHidden', () => {
|
||||||
it('should return true/false when section is hidden/visible', () => {
|
it('should return true/false when section is hidden/visible', () => {
|
||||||
let section: any = {
|
let section: any = {
|
||||||
|
@@ -45,6 +45,7 @@ import { RequestService } from '../core/data/request.service';
|
|||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service that provides methods used in submission process.
|
* A service that provides methods used in submission process.
|
||||||
@@ -82,7 +83,8 @@ export class SubmissionService {
|
|||||||
protected store: Store<SubmissionState>,
|
protected store: Store<SubmissionState>,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
protected searchService: SearchService,
|
protected searchService: SearchService,
|
||||||
protected requestService: RequestService) {
|
protected requestService: RequestService,
|
||||||
|
protected jsonPatchOperationService: SubmissionJsonPatchOperationsService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -429,6 +431,16 @@ export class SubmissionService {
|
|||||||
startWith(false));
|
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
|
* Return the visibility status of the specified section
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user