68346: Tests and JSDocs

This commit is contained in:
Kristof De Langhe
2020-02-10 17:51:37 +01:00
parent a638055d12
commit 3dd433a5da
7 changed files with 384 additions and 120 deletions

View File

@@ -1,25 +1,14 @@
import { ItemEditBitstreamBundleComponent } from './item-edit-bitstream-bundle.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { TranslateModule } from '@ngx-translate/core';
import { VarDirective } from '../../../../shared/utils/var.directive';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { createMockRDObs } from '../item-bitstreams.component.spec';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BundleDataService } from '../../../../core/data/bundle-data.service';
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { ObjectValuesPipe } from '../../../../shared/utils/object-values-pipe';
import { NO_ERRORS_SCHEMA, ViewContainerRef } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { Bundle } from '../../../../core/shared/bundle.model';
describe('ItemEditBitstreamBundleComponent', () => {
let comp: ItemEditBitstreamBundleComponent;
let fixture: ComponentFixture<ItemEditBitstreamBundleComponent>;
let objectUpdatesService: ObjectUpdatesService;
let bundleService: BundleDataService;
let viewContainerRef: ViewContainerRef;
const item = Object.assign(new Item(), {
id: 'item-1',
@@ -30,75 +19,12 @@ describe('ItemEditBitstreamBundleComponent', () => {
uuid: 'bundle-1',
self: 'bundle-1-selflink'
});
const date = new Date();
const format = Object.assign(new BitstreamFormat(), {
shortDescription: 'PDF'
});
const bitstream1 = Object.assign(new Bitstream(), {
uuid: 'bitstreamUUID1',
name: 'Fake Bitstream 1',
bundleName: 'ORIGINAL',
description: 'Description',
format: createMockRDObs(format)
});
const fieldUpdate1 = {
field: bitstream1,
changeType: undefined
};
const bitstream2 = Object.assign(new Bitstream(), {
uuid: 'bitstreamUUID2',
name: 'Fake Bitstream 2',
bundleName: 'ORIGINAL',
description: 'Description',
format: createMockRDObs(format)
});
const fieldUpdate2 = {
field: bitstream2,
changeType: undefined
};
const batchSize = 10;
beforeEach(async(() => {
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
{
getFieldUpdates: observableOf({
[bitstream1.uuid]: fieldUpdate1,
[bitstream2.uuid]: fieldUpdate2,
}),
getFieldUpdatesExclusive: observableOf({
[bitstream1.uuid]: fieldUpdate1,
[bitstream2.uuid]: fieldUpdate2,
}),
getFieldUpdatesByCustomOrder: observableOf({
[bitstream1.uuid]: fieldUpdate1,
[bitstream2.uuid]: fieldUpdate2,
}),
saveMoveFieldUpdate: {},
saveRemoveFieldUpdate: {},
removeSingleFieldUpdate: {},
saveAddFieldUpdate: {},
discardFieldUpdates: {},
reinstateFieldUpdates: observableOf(true),
initialize: {},
getUpdatedFields: observableOf([bitstream1, bitstream2]),
getLastModified: observableOf(date),
hasUpdates: observableOf(true),
isReinstatable: observableOf(false),
isValidPage: observableOf(true)
}
);
bundleService = jasmine.createSpyObj('bundleService', {
getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2]))
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ItemEditBitstreamBundleComponent, VarDirective, ObjectValuesPipe],
providers: [
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
{ provide: BundleDataService, useValue: bundleService }
], schemas: [
declarations: [ItemEditBitstreamBundleComponent],
schemas: [
NO_ERRORS_SCHEMA
]
}).compileComponents();
@@ -109,35 +35,12 @@ describe('ItemEditBitstreamBundleComponent', () => {
comp = fixture.componentInstance;
comp.item = item;
comp.bundle = bundle;
comp.batchSize = batchSize;
viewContainerRef = (comp as any).viewContainerRef;
spyOn(viewContainerRef, 'createEmbeddedView');
fixture.detectChanges();
});
describe('A drag-and-drop event', () => {
it('should send a move update to the objectUpdatesService', () => {
const event = {
previousIndex: 0,
currentIndex: 1
};
comp.drop(event as any);
expect(objectUpdatesService.saveMoveFieldUpdate).toHaveBeenCalledWith(bundle.self, event.previousIndex, event.currentIndex);
});
});
describe('loadMore', () => {
it('should increase the current size by ' + batchSize, () => {
const initialSize = comp.currentSize$.value;
comp.loadMore();
const newSize = comp.currentSize$.value;
expect(initialSize + batchSize).toEqual(newSize);
});
});
describe('loadAll', () => {
it('should increase the current size by a lot', () => {
comp.loadAll();
const newSize = comp.currentSize$.value;
expect(newSize).toBeGreaterThanOrEqual(999);
});
it('should create an embedded view of the component', () => {
expect(viewContainerRef.createEmbeddedView).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,120 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Bundle } from '../../../../../core/shared/bundle.model';
import { TranslateModule } from '@ngx-translate/core';
import { PaginatedDragAndDropBitstreamListComponent } from './paginated-drag-and-drop-bitstream-list.component';
import { VarDirective } from '../../../../../shared/utils/var.directive';
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
import { createMockRDObs } from '../../item-bitstreams.component.spec';
import { Bitstream } from '../../../../../core/shared/bitstream.model';
import { BitstreamFormat } from '../../../../../core/shared/bitstream-format.model';
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { take } from 'rxjs/operators';
describe('PaginatedDragAndDropBitstreamListComponent', () => {
let comp: PaginatedDragAndDropBitstreamListComponent;
let fixture: ComponentFixture<PaginatedDragAndDropBitstreamListComponent>;
let objectUpdatesService: ObjectUpdatesService;
let bundleService: BundleDataService;
const bundle = Object.assign(new Bundle(), {
id: 'bundle-1',
uuid: 'bundle-1',
self: 'bundle-1-selflink'
});
const date = new Date();
const format = Object.assign(new BitstreamFormat(), {
shortDescription: 'PDF'
});
const bitstream1 = Object.assign(new Bitstream(), {
uuid: 'bitstreamUUID1',
name: 'Fake Bitstream 1',
bundleName: 'ORIGINAL',
description: 'Description',
format: createMockRDObs(format)
});
const fieldUpdate1 = {
field: bitstream1,
changeType: undefined
};
const bitstream2 = Object.assign(new Bitstream(), {
uuid: 'bitstreamUUID2',
name: 'Fake Bitstream 2',
bundleName: 'ORIGINAL',
description: 'Description',
format: createMockRDObs(format)
});
const fieldUpdate2 = {
field: bitstream2,
changeType: undefined
};
beforeEach(async(() => {
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
{
getFieldUpdates: observableOf({
[bitstream1.uuid]: fieldUpdate1,
[bitstream2.uuid]: fieldUpdate2,
}),
getFieldUpdatesExclusive: observableOf({
[bitstream1.uuid]: fieldUpdate1,
[bitstream2.uuid]: fieldUpdate2,
}),
getFieldUpdatesByCustomOrder: observableOf({
[bitstream1.uuid]: fieldUpdate1,
[bitstream2.uuid]: fieldUpdate2,
}),
saveMoveFieldUpdate: {},
saveRemoveFieldUpdate: {},
removeSingleFieldUpdate: {},
saveAddFieldUpdate: {},
discardFieldUpdates: {},
reinstateFieldUpdates: observableOf(true),
initialize: {},
getUpdatedFields: observableOf([bitstream1, bitstream2]),
getLastModified: observableOf(date),
hasUpdates: observableOf(true),
isReinstatable: observableOf(false),
isValidPage: observableOf(true),
initializeWithCustomOrder: {},
addPageToCustomOrder: {}
}
);
bundleService = jasmine.createSpyObj('bundleService', {
getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2]))
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [PaginatedDragAndDropBitstreamListComponent, VarDirective, ObjectValuesPipe],
providers: [
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
{ provide: BundleDataService, useValue: bundleService }
], schemas: [
NO_ERRORS_SCHEMA
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PaginatedDragAndDropBitstreamListComponent);
comp = fixture.componentInstance;
comp.bundle = bundle;
fixture.detectChanges();
});
it('should initialize the objectsRD$', (done) => {
comp.objectsRD$.pipe(take(1)).subscribe((objects) => {
expect(objects.payload.page).toEqual([bitstream1, bitstream2]);
done();
});
});
it('should initialize the URL', () => {
expect(comp.url).toEqual(bundle.self);
});
});

View File

@@ -1,6 +1,6 @@
import * as deepFreeze from 'deep-freeze';
import {
AddFieldUpdateAction,
AddFieldUpdateAction, AddPageToCustomOrderAction,
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction, MoveFieldUpdateAction,
@@ -81,8 +81,13 @@ describe('objectUpdatesReducer', () => {
},
lastModified: modDate,
customOrder: {
initialOrder: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid],
newOrder: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid],
initialOrderPages: [
{ order: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid] }
],
newOrderPages: [
{ order: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid] }
],
pageSize: 10,
changed: false
}
}
@@ -109,8 +114,13 @@ describe('objectUpdatesReducer', () => {
},
lastModified: modDate,
customOrder: {
initialOrder: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid],
newOrder: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid],
initialOrderPages: [
{ order: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid] }
],
newOrderPages: [
{ order: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid] }
],
pageSize: 10,
changed: false
}
},
@@ -145,8 +155,13 @@ describe('objectUpdatesReducer', () => {
},
lastModified: modDate,
customOrder: {
initialOrder: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid],
newOrder: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid],
initialOrderPages: [
{ order: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid] }
],
newOrderPages: [
{ order: [identifiable1.uuid, identifiable2.uuid, identifiable3.uuid] }
],
pageSize: 10,
changed: false
}
}
@@ -211,7 +226,7 @@ describe('objectUpdatesReducer', () => {
});
it('should initialize all fields when the INITIALIZE action is dispatched, based on the payload', () => {
const action = new InitializeFieldsAction(url, [identifiable1, identifiable3], modDate);
const action = new InitializeFieldsAction(url, [identifiable1, identifiable3], modDate, [identifiable1.uuid, identifiable3.uuid], 10, 0);
const expectedState = {
[url]: {
@@ -230,8 +245,13 @@ describe('objectUpdatesReducer', () => {
fieldUpdates: {},
lastModified: modDate,
customOrder: {
initialOrder: [],
newOrder: [],
initialOrderPages: [
{ order: [identifiable1.uuid, identifiable3.uuid] }
],
newOrderPages: [
{ order: [identifiable1.uuid, identifiable3.uuid] }
],
pageSize: 10,
changed: false
}
}
@@ -301,11 +321,28 @@ describe('objectUpdatesReducer', () => {
});
it('should move the custom order from the state when the MOVE action is dispatched', () => {
const action = new MoveFieldUpdateAction(url, 0, 1);
const action = new MoveFieldUpdateAction(url, 0, 1, 0, 0);
const newState = objectUpdatesReducer(testState, action);
expect(newState[url].customOrder.newOrder[0]).toEqual(testState[url].customOrder.newOrder[1]);
expect(newState[url].customOrder.newOrder[1]).toEqual(testState[url].customOrder.newOrder[0]);
expect(newState[url].customOrder.newOrderPages[0].order[0]).toEqual(testState[url].customOrder.newOrderPages[0].order[1]);
expect(newState[url].customOrder.newOrderPages[0].order[1]).toEqual(testState[url].customOrder.newOrderPages[0].order[0]);
expect(newState[url].customOrder.changed).toEqual(true);
});
it('should add a new page to the custom order and add empty pages in between when the ADD_PAGE_TO_CUSTOM_ORDER action is dispatched', () => {
const identifiable4 = {
uuid: 'a23eae5a-7857-4ef9-8e52-989436ad2955',
key: 'dc.description.abstract',
language: null,
value: 'Extra value'
};
const action = new AddPageToCustomOrderAction(url, [identifiable4], [identifiable4.uuid], 2);
const newState = objectUpdatesReducer(testState, action);
// Confirm the page in between the two pages (index 1) has been filled with 10 (page size) undefined values
expect(newState[url].customOrder.newOrderPages[1].order.length).toEqual(10);
expect(newState[url].customOrder.newOrderPages[1].order[0]).toBeUndefined();
// Verify the new page is correct
expect(newState[url].customOrder.newOrderPages[2].order[0]).toEqual(identifiable4.uuid);
});
});

View File

@@ -2,6 +2,7 @@ import { Store } from '@ngrx/store';
import { CoreState } from '../../core.reducers';
import { ObjectUpdatesService } from './object-updates.service';
import {
AddPageToCustomOrderAction,
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction, ReinstateObjectUpdatesAction, RemoveFieldUpdateAction,
@@ -58,6 +59,25 @@ describe('ObjectUpdatesService', () => {
});
});
describe('initializeWithCustomOrder', () => {
const pageSize = 20;
const page = 0;
it('should dispatch an INITIALIZE action with the correct URL, initial identifiables, last modified , custom order, page size and page', () => {
service.initializeWithCustomOrder(url, identifiables, modDate, pageSize, page);
expect(store.dispatch).toHaveBeenCalledWith(new InitializeFieldsAction(url, identifiables, modDate, identifiables.map((identifiable) => identifiable.uuid), pageSize, page));
});
});
describe('addPageToCustomOrder', () => {
const page = 2;
it('should dispatch an ADD_PAGE_TO_CUSTOM_ORDER action with the correct URL, identifiables, custom order and page number to add', () => {
service.addPageToCustomOrder(url, identifiables, page);
expect(store.dispatch).toHaveBeenCalledWith(new AddPageToCustomOrderAction(url, identifiables, identifiables.map((identifiable) => identifiable.uuid), page));
});
});
describe('getFieldUpdates', () => {
it('should return the list of all fields, including their update if there is one', () => {
const result$ = service.getFieldUpdates(url, identifiables);

View File

@@ -73,6 +73,12 @@ export class ObjectUpdatesService {
this.store.dispatch(new InitializeFieldsAction(url, fields, lastModified, fields.map((field) => field.uuid), pageSize, page));
}
/**
* Method to dispatch an AddPageToCustomOrderAction, adding a new page to an already existing custom order tracking
* @param url The URL for which the changes are being mapped
* @param fields The fields to add a new page for
* @param page The page number (starting from index 0)
*/
addPageToCustomOrder(url, fields: Identifiable[], page: number): void {
this.store.dispatch(new AddPageToCustomOrderAction(url, fields, fields.map((field) => field.uuid), page));
}

View File

@@ -0,0 +1,178 @@
import { AbstractPaginatedDragAndDropListComponent } from './abstract-paginated-drag-and-drop-list.component';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
import { ElementRef } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { PaginatedList } from '../../core/data/paginated-list';
import { RemoteData } from '../../core/data/remote-data';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { createPaginatedList, createSuccessfulRemoteDataObject } from '../testing/utils';
import { FieldUpdates } from '../../core/data/object-updates/object-updates.reducer';
import { of as observableOf } from 'rxjs';
import { take } from 'rxjs/operators';
import { PaginationComponent } from '../pagination/pagination.component';
class MockAbstractPaginatedDragAndDropListComponent extends AbstractPaginatedDragAndDropListComponent<DSpaceObject> {
constructor(protected objectUpdatesService: ObjectUpdatesService,
protected elRef: ElementRef,
protected mockUrl: string,
protected mockObjectsRD$: Observable<RemoteData<PaginatedList<DSpaceObject>>>) {
super(objectUpdatesService, elRef);
}
initializeObjectsRD(): void {
this.objectsRD$ = this.mockObjectsRD$;
}
initializeURL(): void {
this.url = this.mockUrl;
}
}
describe('AbstractPaginatedDragAndDropListComponent', () => {
let component: MockAbstractPaginatedDragAndDropListComponent;
let objectUpdatesService: ObjectUpdatesService;
let elRef: ElementRef;
const url = 'mock-abstract-paginated-drag-and-drop-list-component';
const object1 = Object.assign(new DSpaceObject(), { uuid: 'object-1' });
const object2 = Object.assign(new DSpaceObject(), { uuid: 'object-2' });
const objectsRD = createSuccessfulRemoteDataObject(createPaginatedList([object1, object2]));
let objectsRD$: BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>>;
const updates = {
[object1.uuid]: { field: object1, changeType: undefined },
[object2.uuid]: { field: object2, changeType: undefined }
} as FieldUpdates;
let paginationComponent: PaginationComponent;
beforeEach(() => {
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', {
initializeWithCustomOrder: {},
addPageToCustomOrder: {},
getFieldUpdatesByCustomOrder: observableOf(updates),
saveMoveFieldUpdate: {}
});
elRef = {
nativeElement: jasmine.createSpyObj('nativeElement', {
querySelector: {}
})
};
paginationComponent = jasmine.createSpyObj('paginationComponent', {
doPageChange: {}
});
objectsRD$ = new BehaviorSubject(objectsRD);
component = new MockAbstractPaginatedDragAndDropListComponent(objectUpdatesService, elRef, url, objectsRD$);
component.paginationComponent = paginationComponent;
component.ngOnInit();
});
it('should call initializeWithCustomOrder to initialize the first page and add it to initializedPages', (done) => {
expect(component.initializedPages.indexOf(0)).toBeLessThan(0);
component.updates$.pipe(take(1)).subscribe(() => {
expect(objectUpdatesService.initializeWithCustomOrder).toHaveBeenCalled();
expect(component.initializedPages.indexOf(0)).toBeGreaterThanOrEqual(0);
done();
});
});
it('should initialize the updates correctly', (done) => {
component.updates$.pipe(take(1)).subscribe((fieldUpdates) => {
expect(fieldUpdates).toEqual(updates);
done();
});
});
describe('when a new page is loaded', () => {
const page = 5;
beforeEach((done) => {
component.updates$.pipe(take(1)).subscribe(() => {
component.currentPage$.next(page);
objectsRD$.next(objectsRD);
done();
});
});
it('should call addPageToCustomOrder to initialize the new page and add it to initializedPages', (done) => {
component.updates$.pipe(take(1)).subscribe(() => {
expect(objectUpdatesService.addPageToCustomOrder).toHaveBeenCalled();
expect(component.initializedPages.indexOf(page - 1)).toBeGreaterThanOrEqual(0);
done();
});
});
describe('twice', () => {
beforeEach((done) => {
component.updates$.pipe(take(1)).subscribe(() => {
component.currentPage$.next(page);
objectsRD$.next(objectsRD);
done();
});
});
it('shouldn\'t call addPageToCustomOrder again, as the page has already been initialized', (done) => {
component.updates$.pipe(take(1)).subscribe(() => {
expect(objectUpdatesService.addPageToCustomOrder).toHaveBeenCalledTimes(1);
done();
});
});
});
});
describe('switchPage', () => {
const page = 3;
beforeEach(() => {
component.switchPage(page);
});
it('should set currentPage$ to the new page', () => {
expect(component.currentPage$.value).toEqual(page);
});
});
describe('drop', () => {
const event = {
previousIndex: 0,
currentIndex: 1,
item: { element: { nativeElement: { id: object1.uuid } } }
} as any;
describe('when the user is hovering over a new page', () => {
const hoverPage = 3;
const hoverElement = { textContent: '' + hoverPage };
beforeEach(() => {
elRef.nativeElement.querySelector.and.returnValue(hoverElement);
component.initializedPages.push(hoverPage - 1);
component.drop(event);
});
it('should detect the page and set currentPage$ to its value', () => {
expect(component.currentPage$.value).toEqual(hoverPage);
});
it('should detect the page and update the pagination component with its value', () => {
expect(paginationComponent.doPageChange).toHaveBeenCalledWith(hoverPage);
});
it('should send out a saveMoveFieldUpdate with the correct values', () => {
expect(objectUpdatesService.saveMoveFieldUpdate).toHaveBeenCalledWith(url, event.previousIndex, 0, 0, hoverPage - 1, object1);
});
});
describe('when the user is not hovering over a new page', () => {
beforeEach(() => {
component.drop(event);
});
it('should send out a saveMoveFieldUpdate with the correct values', () => {
expect(objectUpdatesService.saveMoveFieldUpdate).toHaveBeenCalledWith(url, event.previousIndex, event.currentIndex, 0, 0);
});
});
});
});

View File

@@ -6,7 +6,7 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
import { switchMap, take, tap } from 'rxjs/operators';
import { hasValue, isEmpty } from '../empty.util';
import { hasValue, isEmpty, isNotEmpty } from '../empty.util';
import { paginatedListToArray } from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
@@ -152,7 +152,7 @@ export abstract class AbstractPaginatedDragAndDropListComponent<T extends DSpace
drop(event: CdkDragDrop<any>) {
// Check if the user is hovering over any of the pagination's pages at the time of dropping the object
const droppedOnElement = this.elRef.nativeElement.querySelector('.page-item:hover');
if (hasValue(droppedOnElement)) {
if (isNotEmpty(droppedOnElement)) {
// The user is hovering over a page, fetch the page's number from the element
const page = Number(droppedOnElement.textContent);
if (hasValue(page) && !Number.isNaN(page)) {