forked from hazza/dspace-angular
Add Item Status Edit Actions
Add the Item Withdraw and Reistate action Add the make Item Private and Public action Add the Permanently Delete action
This commit is contained in:
@@ -43,6 +43,120 @@
|
|||||||
"simple": "Simple item page",
|
"simple": "Simple item page",
|
||||||
"full": "Full item page"
|
"full": "Full item page"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
"table": {
|
||||||
|
"collection": "Collection",
|
||||||
|
"author": "Author",
|
||||||
|
"title": "Title"
|
||||||
|
},
|
||||||
|
"confirm": "Confirm selected"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"head": "Edit Item",
|
||||||
|
"tabs": {
|
||||||
|
"status": {
|
||||||
|
"head": "Item Status",
|
||||||
|
"description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.",
|
||||||
|
"labels": {
|
||||||
|
"id": "Item Internal ID",
|
||||||
|
"handle": "Handle",
|
||||||
|
"lastModified": "Last Modified",
|
||||||
|
"itemPage": "Item Page"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"authorizations": {
|
||||||
|
"label": "Edit item's authorization policies",
|
||||||
|
"button": "Authorizations..."
|
||||||
|
},
|
||||||
|
"withdraw": {
|
||||||
|
"label": "Withdraw item from the repository",
|
||||||
|
"button": "Withdraw..."
|
||||||
|
},
|
||||||
|
"reinstate": {
|
||||||
|
"label": "Reinstate item into the repository",
|
||||||
|
"button": "Reinstate..."
|
||||||
|
},
|
||||||
|
"move": {
|
||||||
|
"label": "Move item to another collection",
|
||||||
|
"button": "Move..."
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"label": "Make item private",
|
||||||
|
"button": "Make it private..."
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"label": "Make item public",
|
||||||
|
"button": "Make it public..."
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"label": "Completely expunge item",
|
||||||
|
"button": "Permanently delete"
|
||||||
|
},
|
||||||
|
"mappedCollections": {
|
||||||
|
"label": "Manage mapped collections",
|
||||||
|
"button": "Mapped collections"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bitstreams": {
|
||||||
|
"head": "Item Bitstreams"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"head": "Item Metadata"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"head": "View Item"
|
||||||
|
},
|
||||||
|
"curate": {
|
||||||
|
"head": "Curate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modify.overview": {
|
||||||
|
"field": "Field",
|
||||||
|
"value": "Value",
|
||||||
|
"language": "Language"
|
||||||
|
},
|
||||||
|
"withdraw": {
|
||||||
|
"header": "Withdraw item: {{ id }}",
|
||||||
|
"description": "Are you sure this item should be withdrawn from the archive?",
|
||||||
|
"confirm": "Withdraw",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"success": "The item was withdrawn successfully",
|
||||||
|
"error": "An error occured while withdrawing the item"
|
||||||
|
},
|
||||||
|
"reinstate": {
|
||||||
|
"header": "Reinstate item: {{ id }}",
|
||||||
|
"description": "Are you sure this item should be reinstated to the archive?",
|
||||||
|
"confirm": "Reinstate",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"success": "The item was reinstated successfully",
|
||||||
|
"error": "An error occured while reinstating the item"
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"header": "Make item private: {{ id }}",
|
||||||
|
"description": "Are you sure this item should be made private in the archive?",
|
||||||
|
"confirm": "Make it Private",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"success": "The item is now private",
|
||||||
|
"error": "An error occured while making the item private"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"header": "Make item public: {{ id }}",
|
||||||
|
"description": "Are you sure this item should be made public in the archive?",
|
||||||
|
"confirm": "Make it Public",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"success": "The item is now public",
|
||||||
|
"error": "An error occured while making the item public"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete item: {{ id }}",
|
||||||
|
"description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.",
|
||||||
|
"confirm": "Delete",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"success": "The item has been deleted",
|
||||||
|
"error": "An error occured while deleting the item"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
import {RemoteData} from '../../core/data/remote-data';
|
||||||
|
import {hot} from 'jasmine-marbles';
|
||||||
|
import {Item} from '../../core/shared/item.model';
|
||||||
|
import {findSuccessfulAccordingTo} from './edit-item-operators';
|
||||||
|
|
||||||
|
describe('findSuccessfulAccordingTo', () => {
|
||||||
|
let mockItem1;
|
||||||
|
let mockItem2;
|
||||||
|
let predicate;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockItem1 = new Item();
|
||||||
|
mockItem1.isWithdrawn = true;
|
||||||
|
|
||||||
|
mockItem2 = new Item();
|
||||||
|
mockItem1.isWithdrawn = false;
|
||||||
|
|
||||||
|
predicate = (rd: RemoteData<Item>) => rd.payload.isWithdrawn;
|
||||||
|
});
|
||||||
|
it('should return first successful RemoteData Observable that complies to predicate', () => {
|
||||||
|
const testRD = {
|
||||||
|
a: new RemoteData(false, false, true, null, undefined),
|
||||||
|
b: new RemoteData(false, false, false, null, mockItem1),
|
||||||
|
c: new RemoteData(false, false, true, null, mockItem2),
|
||||||
|
d: new RemoteData(false, false, true, null, mockItem1),
|
||||||
|
e: new RemoteData(false, false, true, null, mockItem2),
|
||||||
|
};
|
||||||
|
|
||||||
|
const source = hot('abcde', testRD);
|
||||||
|
const result = source.pipe(findSuccessfulAccordingTo(predicate));
|
||||||
|
|
||||||
|
result.subscribe((value) => expect(value).toEqual(testRD.d));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
13
src/app/+item-page/edit-item-page/edit-item-operators.ts
Normal file
13
src/app/+item-page/edit-item-page/edit-item-operators.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {RemoteData} from '../../core/data/remote-data';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
|
import {getAllSucceededRemoteData} from '../../core/shared/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return first Observable of a RemoteData object that complies to the provided predicate
|
||||||
|
* @param predicate
|
||||||
|
*/
|
||||||
|
export const findSuccessfulAccordingTo = <T>(predicate: (rd: RemoteData<T>) => boolean) =>
|
||||||
|
(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
|
source.pipe(getAllSucceededRemoteData(),
|
||||||
|
first(predicate));
|
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 class="border-bottom">{{'item.edit.head' | translate}}</h2>
|
||||||
|
<div class="pt-2">
|
||||||
|
<ngb-tabset>
|
||||||
|
<ngb-tab title="{{'item.edit.tabs.status.head' | translate}}">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<ds-item-status [item]="(itemRD$ | async)?.payload"></ds-item-status>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
<ngb-tab title="{{'item.edit.tabs.bitstreams.head' | translate}}">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
<ngb-tab title="{{'item.edit.tabs.metadata.head' | translate}}">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
<ngb-tab title="{{'item.edit.tabs.view.head' | translate}}">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
<ngb-tab title="{{'item.edit.tabs.curate.head' | translate}}">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
</ngb-tabset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,35 @@
|
|||||||
|
import {fadeIn, fadeInOut} from '../../shared/animations/fade';
|
||||||
|
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import {RemoteData} from '../../core/data/remote-data';
|
||||||
|
import {Item} from '../../core/shared/item.model';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {map} from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-edit-item-page',
|
||||||
|
templateUrl: './edit-item-page.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
animations: [
|
||||||
|
fadeIn,
|
||||||
|
fadeInOut
|
||||||
|
]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Page component for editing an item
|
||||||
|
*/
|
||||||
|
export class EditItemPageComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to edit
|
||||||
|
*/
|
||||||
|
itemRD$: Observable<RemoteData<Item>>;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/app/+item-page/edit-item-page/edit-item-page.module.ts
Normal file
37
src/app/+item-page/edit-item-page/edit-item-page.module.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {SharedModule} from '../../shared/shared.module';
|
||||||
|
import {EditItemPageRoutingModule} from './edit-item-page.routing.module';
|
||||||
|
import {EditItemPageComponent} from './edit-item-page.component';
|
||||||
|
import {ItemStatusComponent} from './item-status/item-status.component';
|
||||||
|
import {ItemOperationComponent} from './item-operation/item-operation.component';
|
||||||
|
import {ModifyItemOverviewComponent} from './modify-item-overview/modify-item-overview.component';
|
||||||
|
import {ItemWithdrawComponent} from './item-withdraw/item-withdraw.component';
|
||||||
|
import {ItemReinstateComponent} from './item-reinstate/item-reinstate.component';
|
||||||
|
import {AbstractSimpleItemActionComponent} from './simple-item-action/abstract-simple-item-action.component';
|
||||||
|
import {ItemPrivateComponent} from './item-private/item-private.component';
|
||||||
|
import {ItemPublicComponent} from './item-public/item-public.component';
|
||||||
|
import {ItemDeleteComponent} from './item-delete/item-delete.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
EditItemPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EditItemPageComponent,
|
||||||
|
ItemOperationComponent,
|
||||||
|
AbstractSimpleItemActionComponent,
|
||||||
|
ModifyItemOverviewComponent,
|
||||||
|
ItemWithdrawComponent,
|
||||||
|
ItemReinstateComponent,
|
||||||
|
ItemPrivateComponent,
|
||||||
|
ItemPublicComponent,
|
||||||
|
ItemDeleteComponent,
|
||||||
|
ItemStatusComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class EditItemPageModule {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
import {ItemPageResolver} from '../item-page.resolver';
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
import {EditItemPageComponent} from './edit-item-page.component';
|
||||||
|
import {ItemWithdrawComponent} from './item-withdraw/item-withdraw.component';
|
||||||
|
import {ItemReinstateComponent} from './item-reinstate/item-reinstate.component';
|
||||||
|
import {ItemPrivateComponent} from './item-private/item-private.component';
|
||||||
|
import {ItemPublicComponent} from './item-public/item-public.component';
|
||||||
|
import {ItemDeleteComponent} from './item-delete/item-delete.component';
|
||||||
|
|
||||||
|
const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
||||||
|
const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
||||||
|
const ITEM_EDIT_PRIVATE_PATH = 'private';
|
||||||
|
const ITEM_EDIT_PUBLIC_PATH = 'public';
|
||||||
|
const ITEM_EDIT_DELETE_PATH = 'delete';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: EditItemPageComponent,
|
||||||
|
resolve: {
|
||||||
|
item: ItemPageResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_WITHDRAW_PATH,
|
||||||
|
component: ItemWithdrawComponent,
|
||||||
|
resolve: {
|
||||||
|
item: ItemPageResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_REINSTATE_PATH,
|
||||||
|
component: ItemReinstateComponent,
|
||||||
|
resolve: {
|
||||||
|
item: ItemPageResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_PRIVATE_PATH,
|
||||||
|
component: ItemPrivateComponent,
|
||||||
|
resolve: {
|
||||||
|
item: ItemPageResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_PUBLIC_PATH,
|
||||||
|
component: ItemPublicComponent,
|
||||||
|
resolve: {
|
||||||
|
item: ItemPageResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_DELETE_PATH,
|
||||||
|
component: ItemDeleteComponent,
|
||||||
|
resolve: {
|
||||||
|
item: ItemPageResolver
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ItemPageResolver,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class EditItemPageRoutingModule {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
|
import {of as observableOf} from 'rxjs';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
import {ItemDeleteComponent} from './item-delete.component';
|
||||||
|
import {getItemEditPath} from '../../item-page-routing.module';
|
||||||
|
|
||||||
|
let comp: ItemDeleteComponent;
|
||||||
|
let fixture: ComponentFixture<ItemDeleteComponent>;
|
||||||
|
|
||||||
|
let mockItem;
|
||||||
|
let itemPageUrl;
|
||||||
|
let routerStub;
|
||||||
|
let mockItemDataService: ItemDataService;
|
||||||
|
let routeStub;
|
||||||
|
let notificationsServiceStub;
|
||||||
|
let successfulRestResponse;
|
||||||
|
let failRestResponse;
|
||||||
|
|
||||||
|
describe('ItemDeleteComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
mockItemDataService = jasmine.createSpyObj('mockItemDataService', {
|
||||||
|
delete: observableOf(new RestResponse(true, '200'))
|
||||||
|
});
|
||||||
|
|
||||||
|
routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: new RemoteData(false, false, true, null, {
|
||||||
|
id: 'fake-id'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
|
declarations: [ItemDeleteComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: Router, useValue: routerStub},
|
||||||
|
{provide: ItemDataService, useValue: mockItemDataService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsServiceStub},
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
successfulRestResponse = new RestResponse(true, '200');
|
||||||
|
failRestResponse = new RestResponse(false, '500');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ItemDeleteComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a page with messages based on the \'delete\' messageKey', () => {
|
||||||
|
const header = fixture.debugElement.query(By.css('h2')).nativeElement;
|
||||||
|
expect(header.innerHTML).toContain('item.edit.delete.header');
|
||||||
|
const description = fixture.debugElement.query(By.css('p')).nativeElement;
|
||||||
|
expect(description.innerHTML).toContain('item.edit.delete.description');
|
||||||
|
const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement;
|
||||||
|
expect(confirmButton.innerHTML).toContain('item.edit.delete.confirm');
|
||||||
|
const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement;
|
||||||
|
expect(cancelButton.innerHTML).toContain('item.edit.delete.cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performAction', () => {
|
||||||
|
it('should call delete function from the ItemDataService', () => {
|
||||||
|
spyOn(comp, 'processRestResponse');
|
||||||
|
comp.performAction();
|
||||||
|
|
||||||
|
expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id);
|
||||||
|
expect(comp.processRestResponse).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('processRestResponse', () => {
|
||||||
|
it('should navigate to the homepage on successful deletion of the item', () => {
|
||||||
|
comp.processRestResponse(successfulRestResponse);
|
||||||
|
expect(routerStub.navigate).toHaveBeenCalledWith(['']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('processRestResponse', () => {
|
||||||
|
it('should navigate to the item edit page on failed deletion of the item', () => {
|
||||||
|
comp.processRestResponse(failRestResponse);
|
||||||
|
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditPath('fake-id')]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
@@ -0,0 +1,43 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
|
import {getItemEditPath} from '../../item-page-routing.module';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-delete',
|
||||||
|
templateUrl: '../simple-item-action/abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the item delete page
|
||||||
|
*/
|
||||||
|
export class ItemDeleteComponent extends AbstractSimpleItemActionComponent {
|
||||||
|
|
||||||
|
protected messageKey = 'delete';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the delete action to the item
|
||||||
|
*/
|
||||||
|
performAction() {
|
||||||
|
this.itemDataService.delete(this.item.id).pipe(first()).subscribe(
|
||||||
|
(response: RestResponse) => {
|
||||||
|
this.processRestResponse(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the RestResponse retrieved from the server.
|
||||||
|
* When the item is successfully delete, navigate to the homepage, otherwise navigate back to the item edit page
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
processRestResponse(response: RestResponse) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
this.notificationsService.success(this.translateService.get('item.edit.' + this.messageKey + '.success'));
|
||||||
|
this.router.navigate(['']);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get('item.edit.' + this.messageKey + '.error'));
|
||||||
|
this.router.navigate([getItemEditPath(this.item.id)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
<div class="col-3 float-left d-flex h-100 action-label">
|
||||||
|
<span class="justify-content-center align-self-center">
|
||||||
|
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!operation.disabled" class="col-9 float-left action-button">
|
||||||
|
<a class="btn btn-outline-secondary" href="{{operation.operationUrl}}">
|
||||||
|
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="operation.disabled" class="col-9 float-left action-button">
|
||||||
|
<span class="btn btn-danger">
|
||||||
|
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
|
||||||
|
</span>
|
||||||
|
</div>
|
@@ -0,0 +1,45 @@
|
|||||||
|
import {ItemOperation} from './itemOperation.model';
|
||||||
|
import {async, TestBed} from '@angular/core/testing';
|
||||||
|
import {ItemOperationComponent} from './item-operation.component';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
describe('ItemOperationComponent', () => {
|
||||||
|
let itemOperation: ItemOperation;
|
||||||
|
|
||||||
|
let fixture;
|
||||||
|
let comp;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [ItemOperationComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
itemOperation = new ItemOperation('key1', 'url1');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ItemOperationComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.operation = itemOperation;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render operation row', () => {
|
||||||
|
const span = fixture.debugElement.query(By.css('span')).nativeElement;
|
||||||
|
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
|
||||||
|
const link = fixture.debugElement.query(By.css('a')).nativeElement;
|
||||||
|
expect(link.href).toContain('url1');
|
||||||
|
expect(link.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
|
||||||
|
});
|
||||||
|
it('should render disabled operation row', () => {
|
||||||
|
itemOperation.setDisabled(true);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const span = fixture.debugElement.query(By.css('span')).nativeElement;
|
||||||
|
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
|
||||||
|
const span2 = fixture.debugElement.query(By.css('span.btn-danger')).nativeElement;
|
||||||
|
expect(span2.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,15 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {ItemOperation} from './itemOperation.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-operation',
|
||||||
|
templateUrl: './item-operation.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Operation that can be performed on an item
|
||||||
|
*/
|
||||||
|
export class ItemOperationComponent {
|
||||||
|
|
||||||
|
@Input() operation: ItemOperation;
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
export class ItemOperation {
|
||||||
|
|
||||||
|
operationKey: string;
|
||||||
|
operationUrl: string;
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
constructor(operationKey: string, operationUrl: string) {
|
||||||
|
this.operationKey = operationKey;
|
||||||
|
this.operationUrl = operationUrl;
|
||||||
|
this.setDisabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether this operation should be disabled
|
||||||
|
* @param disabled
|
||||||
|
*/
|
||||||
|
setDisabled(disabled: boolean): void {
|
||||||
|
this.disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,105 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
|
import {of as observableOf} from 'rxjs';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
import {ItemPrivateComponent} from './item-private.component';
|
||||||
|
|
||||||
|
let comp: ItemPrivateComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPrivateComponent>;
|
||||||
|
|
||||||
|
let mockItem;
|
||||||
|
let itemPageUrl;
|
||||||
|
let routerStub;
|
||||||
|
let mockItemDataService: ItemDataService;
|
||||||
|
let routeStub;
|
||||||
|
let notificationsServiceStub;
|
||||||
|
let successfulRestResponse;
|
||||||
|
let failRestResponse;
|
||||||
|
|
||||||
|
describe('ItemPrivateComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
mockItemDataService = jasmine.createSpyObj('mockItemDataService',{
|
||||||
|
setDiscoverable: observableOf(new RestResponse(true, '200'))
|
||||||
|
});
|
||||||
|
|
||||||
|
routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: new RemoteData(false, false, true, null, {
|
||||||
|
id: 'fake-id'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
|
declarations: [ItemPrivateComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: Router, useValue: routerStub},
|
||||||
|
{provide: ItemDataService, useValue: mockItemDataService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsServiceStub},
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
successfulRestResponse = new RestResponse(true, '200');
|
||||||
|
failRestResponse = new RestResponse(false, '500');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ItemPrivateComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a page with messages based on the \'private\' messageKey', () => {
|
||||||
|
const header = fixture.debugElement.query(By.css('h2')).nativeElement;
|
||||||
|
expect(header.innerHTML).toContain('item.edit.private.header');
|
||||||
|
const description = fixture.debugElement.query(By.css('p')).nativeElement;
|
||||||
|
expect(description.innerHTML).toContain('item.edit.private.description');
|
||||||
|
const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement;
|
||||||
|
expect(confirmButton.innerHTML).toContain('item.edit.private.confirm');
|
||||||
|
const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement;
|
||||||
|
expect(cancelButton.innerHTML).toContain('item.edit.private.cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performAction', () => {
|
||||||
|
it('should call setDiscoverable function from the ItemDataService', () => {
|
||||||
|
spyOn(comp, 'processRestResponse');
|
||||||
|
comp.performAction();
|
||||||
|
|
||||||
|
expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem.id, false);
|
||||||
|
expect(comp.processRestResponse).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
@@ -0,0 +1,30 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-private',
|
||||||
|
templateUrl: '../simple-item-action/abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the make item private page
|
||||||
|
*/
|
||||||
|
export class ItemPrivateComponent extends AbstractSimpleItemActionComponent {
|
||||||
|
|
||||||
|
protected messageKey = 'private';
|
||||||
|
protected predicate = (rd: RemoteData<Item>) => !rd.payload.isDiscoverable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the make private action to the item
|
||||||
|
*/
|
||||||
|
performAction() {
|
||||||
|
this.itemDataService.setDiscoverable(this.item.id, false).pipe(first()).subscribe(
|
||||||
|
(response: RestResponse) => {
|
||||||
|
this.processRestResponse(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,105 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
|
import {of as observableOf} from 'rxjs';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
import {ItemPublicComponent} from './item-public.component';
|
||||||
|
|
||||||
|
let comp: ItemPublicComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPublicComponent>;
|
||||||
|
|
||||||
|
let mockItem;
|
||||||
|
let itemPageUrl;
|
||||||
|
let routerStub;
|
||||||
|
let mockItemDataService: ItemDataService;
|
||||||
|
let routeStub;
|
||||||
|
let notificationsServiceStub;
|
||||||
|
let successfulRestResponse;
|
||||||
|
let failRestResponse;
|
||||||
|
|
||||||
|
describe('ItemPublicComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
mockItemDataService = jasmine.createSpyObj('mockItemDataService',{
|
||||||
|
setDiscoverable: observableOf(new RestResponse(true, '200'))
|
||||||
|
});
|
||||||
|
|
||||||
|
routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: new RemoteData(false, false, true, null, {
|
||||||
|
id: 'fake-id'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
|
declarations: [ItemPublicComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: Router, useValue: routerStub},
|
||||||
|
{provide: ItemDataService, useValue: mockItemDataService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsServiceStub},
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
successfulRestResponse = new RestResponse(true, '200');
|
||||||
|
failRestResponse = new RestResponse(false, '500');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ItemPublicComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a page with messages based on the \'public\' messageKey', () => {
|
||||||
|
const header = fixture.debugElement.query(By.css('h2')).nativeElement;
|
||||||
|
expect(header.innerHTML).toContain('item.edit.public.header');
|
||||||
|
const description = fixture.debugElement.query(By.css('p')).nativeElement;
|
||||||
|
expect(description.innerHTML).toContain('item.edit.public.description');
|
||||||
|
const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement;
|
||||||
|
expect(confirmButton.innerHTML).toContain('item.edit.public.confirm');
|
||||||
|
const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement;
|
||||||
|
expect(cancelButton.innerHTML).toContain('item.edit.public.cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performAction', () => {
|
||||||
|
it('should call setDiscoverable function from the ItemDataService', () => {
|
||||||
|
spyOn(comp, 'processRestResponse');
|
||||||
|
comp.performAction();
|
||||||
|
|
||||||
|
expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem.id, true);
|
||||||
|
expect(comp.processRestResponse).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
@@ -0,0 +1,30 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-public',
|
||||||
|
templateUrl: '../simple-item-action/abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the make item public page
|
||||||
|
*/
|
||||||
|
export class ItemPublicComponent extends AbstractSimpleItemActionComponent {
|
||||||
|
|
||||||
|
protected messageKey = 'public';
|
||||||
|
protected predicate = (rd: RemoteData<Item>) => rd.payload.isDiscoverable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the make public action to the item
|
||||||
|
*/
|
||||||
|
performAction() {
|
||||||
|
this.itemDataService.setDiscoverable(this.item.id, true).pipe(first()).subscribe(
|
||||||
|
(response: RestResponse) => {
|
||||||
|
this.processRestResponse(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,105 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
|
import {of as observableOf} from 'rxjs';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
import {ItemReinstateComponent} from './item-reinstate.component';
|
||||||
|
|
||||||
|
let comp: ItemReinstateComponent;
|
||||||
|
let fixture: ComponentFixture<ItemReinstateComponent>;
|
||||||
|
|
||||||
|
let mockItem;
|
||||||
|
let itemPageUrl;
|
||||||
|
let routerStub;
|
||||||
|
let mockItemDataService: ItemDataService;
|
||||||
|
let routeStub;
|
||||||
|
let notificationsServiceStub;
|
||||||
|
let successfulRestResponse;
|
||||||
|
let failRestResponse;
|
||||||
|
|
||||||
|
describe('ItemReinstateComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
mockItemDataService = jasmine.createSpyObj('mockItemDataService',{
|
||||||
|
setWithDrawn: observableOf(new RestResponse(true, '200'))
|
||||||
|
});
|
||||||
|
|
||||||
|
routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: new RemoteData(false, false, true, null, {
|
||||||
|
id: 'fake-id'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
|
declarations: [ItemReinstateComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: Router, useValue: routerStub},
|
||||||
|
{provide: ItemDataService, useValue: mockItemDataService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsServiceStub},
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
successfulRestResponse = new RestResponse(true, '200');
|
||||||
|
failRestResponse = new RestResponse(false, '500');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ItemReinstateComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a page with messages based on the \'reinstate\' messageKey', () => {
|
||||||
|
const header = fixture.debugElement.query(By.css('h2')).nativeElement;
|
||||||
|
expect(header.innerHTML).toContain('item.edit.reinstate.header');
|
||||||
|
const description = fixture.debugElement.query(By.css('p')).nativeElement;
|
||||||
|
expect(description.innerHTML).toContain('item.edit.reinstate.description');
|
||||||
|
const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement;
|
||||||
|
expect(confirmButton.innerHTML).toContain('item.edit.reinstate.confirm');
|
||||||
|
const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement;
|
||||||
|
expect(cancelButton.innerHTML).toContain('item.edit.reinstate.cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performAction', () => {
|
||||||
|
it('should call setWithdrawn function from the ItemDataService', () => {
|
||||||
|
spyOn(comp, 'processRestResponse');
|
||||||
|
comp.performAction();
|
||||||
|
|
||||||
|
expect(mockItemDataService.setWithDrawn).toHaveBeenCalledWith(mockItem.id, false);
|
||||||
|
expect(comp.processRestResponse).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
@@ -0,0 +1,30 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-reinstate',
|
||||||
|
templateUrl: '../simple-item-action/abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the Item Reinstate page
|
||||||
|
*/
|
||||||
|
export class ItemReinstateComponent extends AbstractSimpleItemActionComponent {
|
||||||
|
|
||||||
|
protected messageKey = 'reinstate';
|
||||||
|
protected predicate = (rd: RemoteData<Item>) => !rd.payload.isWithdrawn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the reinstate action to the item
|
||||||
|
*/
|
||||||
|
performAction() {
|
||||||
|
this.itemDataService.setWithDrawn(this.item.id, false).pipe(first()).subscribe(
|
||||||
|
(response: RestResponse) => {
|
||||||
|
this.processRestResponse(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
<p class="mt-2">{{'item.edit.tabs.status.description' | translate}}</p>
|
||||||
|
<div class="row">
|
||||||
|
<div *ngFor="let statusKey of statusDataKeys" class="w-100">
|
||||||
|
<div class="col-3 float-left status-label">
|
||||||
|
{{'item.edit.tabs.status.labels.' + statusKey | translate}}:
|
||||||
|
</div>
|
||||||
|
<div class="col-9 float-left status-data" id="status-{{statusKey}}">
|
||||||
|
{{statusData[statusKey]}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 float-left status-label">
|
||||||
|
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
|
||||||
|
</div>
|
||||||
|
<div class="col-9 float-left status-data" id="status-itemPage">
|
||||||
|
<a href="{{getItemPage()}}">{{getItemPage()}}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngFor="let operation of operations" class="w-100 pt-3">
|
||||||
|
<ds-item-operation [operation]="operation"></ds-item-operation>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,68 @@
|
|||||||
|
import { ItemStatusComponent } from './item-status.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
|
||||||
|
import { HostWindowService } from '../../../shared/host-window.service';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { RouterStub } from '../../../shared/testing/router-stub';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
|
||||||
|
describe('ItemStatusComponent', () => {
|
||||||
|
let comp: ItemStatusComponent;
|
||||||
|
let fixture: ComponentFixture<ItemStatusComponent>;
|
||||||
|
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018'
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
const routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
|
declarations: [ItemStatusComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
|
||||||
|
], schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemStatusComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItem;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item\'s internal id', () => {
|
||||||
|
const statusId: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-id')).nativeElement;
|
||||||
|
expect(statusId.textContent).toContain(mockItem.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item\'s handle', () => {
|
||||||
|
const statusHandle: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-handle')).nativeElement;
|
||||||
|
expect(statusHandle.textContent).toContain(mockItem.handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item\'s last modified date', () => {
|
||||||
|
const statusLastModified: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-lastModified')).nativeElement;
|
||||||
|
expect(statusLastModified.textContent).toContain(mockItem.lastModified);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item\'s page url', () => {
|
||||||
|
const statusItemPage: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-itemPage')).nativeElement;
|
||||||
|
expect(statusItemPage.textContent).toContain(itemPageUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,97 @@
|
|||||||
|
import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {fadeIn, fadeInOut} from '../../../shared/animations/fade';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {ItemOperation} from '../item-operation/itemOperation.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-status',
|
||||||
|
templateUrl: './item-status.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
animations: [
|
||||||
|
fadeIn,
|
||||||
|
fadeInOut
|
||||||
|
]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for displaying an item's status
|
||||||
|
*/
|
||||||
|
export class ItemStatusComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display the status for
|
||||||
|
*/
|
||||||
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data to show in the status
|
||||||
|
*/
|
||||||
|
statusData: any;
|
||||||
|
/**
|
||||||
|
* The keys of the data (to loop over)
|
||||||
|
*/
|
||||||
|
statusDataKeys;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible actions that can be performed on the item
|
||||||
|
* key: id value: url to action's component
|
||||||
|
*/
|
||||||
|
operations: ItemOperation[];
|
||||||
|
/**
|
||||||
|
* The keys of the actions (to loop over)
|
||||||
|
*/
|
||||||
|
actionsKeys;
|
||||||
|
|
||||||
|
constructor(private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.statusData = Object.assign({
|
||||||
|
id: this.item.id,
|
||||||
|
handle: this.item.handle,
|
||||||
|
lastModified: this.item.lastModified
|
||||||
|
});
|
||||||
|
this.statusDataKeys = Object.keys(this.statusData);
|
||||||
|
|
||||||
|
/*
|
||||||
|
The key is used to build messages
|
||||||
|
i18n example: 'item.edit.tabs.status.buttons.<key>.label'
|
||||||
|
The value is supposed to be a href for the button
|
||||||
|
*/
|
||||||
|
this.operations = [];
|
||||||
|
this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl() + '/'));
|
||||||
|
this.operations.push(new ItemOperation('move', this.getCurrentUrl() + '/move'));
|
||||||
|
if (this.item.isWithdrawn) {
|
||||||
|
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl() + '/reinstate'));
|
||||||
|
} else {
|
||||||
|
this.operations.push(new ItemOperation('withdraw', this.getCurrentUrl() + '/withdraw'));
|
||||||
|
}
|
||||||
|
if (this.item.isDiscoverable) {
|
||||||
|
this.operations.push(new ItemOperation('private', this.getCurrentUrl() + '/private'));
|
||||||
|
} else {
|
||||||
|
this.operations.push(new ItemOperation('public', this.getCurrentUrl() + '/public'));
|
||||||
|
}
|
||||||
|
this.operations.push(new ItemOperation('delete', this.getCurrentUrl() + '/delete'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url to the simple item page
|
||||||
|
* @returns {string} url
|
||||||
|
*/
|
||||||
|
getItemPage(): string {
|
||||||
|
return this.router.url.substr(0, this.router.url.lastIndexOf('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current url without query params
|
||||||
|
* @returns {string} url
|
||||||
|
*/
|
||||||
|
getCurrentUrl(): string {
|
||||||
|
if (this.router.url.indexOf('?') > -1) {
|
||||||
|
return this.router.url.substr(0, this.router.url.indexOf('?'));
|
||||||
|
} else {
|
||||||
|
return this.router.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,105 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
|
import {of as observableOf} from 'rxjs';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {ItemWithdrawComponent} from './item-withdraw.component';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
let comp: ItemWithdrawComponent;
|
||||||
|
let fixture: ComponentFixture<ItemWithdrawComponent>;
|
||||||
|
|
||||||
|
let mockItem;
|
||||||
|
let itemPageUrl;
|
||||||
|
let routerStub;
|
||||||
|
let mockItemDataService: ItemDataService;
|
||||||
|
let routeStub;
|
||||||
|
let notificationsServiceStub;
|
||||||
|
let successfulRestResponse;
|
||||||
|
let failRestResponse;
|
||||||
|
|
||||||
|
describe('ItemWithdrawComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
mockItemDataService = jasmine.createSpyObj('mockItemDataService',{
|
||||||
|
setWithDrawn: observableOf(new RestResponse(true, '200'))
|
||||||
|
});
|
||||||
|
|
||||||
|
routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: new RemoteData(false, false, true, null, {
|
||||||
|
id: 'fake-id'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot(),],
|
||||||
|
declarations: [ItemWithdrawComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: Router, useValue: routerStub},
|
||||||
|
{provide: ItemDataService, useValue: mockItemDataService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsServiceStub},
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
successfulRestResponse = new RestResponse(true, '200');
|
||||||
|
failRestResponse = new RestResponse(false, '500');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ItemWithdrawComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a page with messages based on the \'withdraw\' messageKey', () => {
|
||||||
|
const header = fixture.debugElement.query(By.css('h2')).nativeElement;
|
||||||
|
expect(header.innerHTML).toContain('item.edit.withdraw.header');
|
||||||
|
const description = fixture.debugElement.query(By.css('p')).nativeElement;
|
||||||
|
expect(description.innerHTML).toContain('item.edit.withdraw.description');
|
||||||
|
const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement;
|
||||||
|
expect(confirmButton.innerHTML).toContain('item.edit.withdraw.confirm');
|
||||||
|
const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement;
|
||||||
|
expect(cancelButton.innerHTML).toContain('item.edit.withdraw.cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performAction', () => {
|
||||||
|
it('should call setWithdrawn function from the ItemDataService', () => {
|
||||||
|
spyOn(comp, 'processRestResponse');
|
||||||
|
comp.performAction();
|
||||||
|
|
||||||
|
expect(mockItemDataService.setWithDrawn).toHaveBeenCalledWith(mockItem.id, true);
|
||||||
|
expect(comp.processRestResponse).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
@@ -0,0 +1,30 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-withdraw',
|
||||||
|
templateUrl: '../simple-item-action/abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the Item Withdraw page
|
||||||
|
*/
|
||||||
|
export class ItemWithdrawComponent extends AbstractSimpleItemActionComponent {
|
||||||
|
|
||||||
|
protected messageKey = 'withdraw';
|
||||||
|
protected predicate = (rd: RemoteData<Item>) => rd.payload.isWithdrawn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the withdraw action to the item
|
||||||
|
*/
|
||||||
|
performAction() {
|
||||||
|
this.itemDataService.setWithDrawn(this.item.id, true).pipe(first()).subscribe(
|
||||||
|
(response: RestResponse) => {
|
||||||
|
this.processRestResponse(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
<table id="metadata" class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{'item.edit.modify.overview.field'| translate}}</th>
|
||||||
|
<th scope="col">{{'item.edit.modify.overview.value'| translate}}</th>
|
||||||
|
<th scope="col">{{'item.edit.modify.overview.language'| translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let metadatum of metadata" class="metadata-row">
|
||||||
|
<td>{{metadatum.key}}</td>
|
||||||
|
<td>{{metadatum.value}}</td>
|
||||||
|
<td>{{metadatum.language}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@@ -0,0 +1,55 @@
|
|||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {ModifyItemOverviewComponent} from './modify-item-overview.component';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
|
||||||
|
let comp: ModifyItemOverviewComponent;
|
||||||
|
let fixture: ComponentFixture<ModifyItemOverviewComponent>;
|
||||||
|
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
metadata: [
|
||||||
|
{key: 'dc.title', value: 'Mock item title', language: 'en'},
|
||||||
|
{key: 'dc.contributor.author', value: 'Mayer, Ed', language: ''}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ModifyItemOverviewComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [ModifyItemOverviewComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ModifyItemOverviewComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItem;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('should render a table of existing metadata fields in the item', () => {
|
||||||
|
|
||||||
|
const metadataRows = fixture.debugElement.queryAll(By.css('tr.metadata-row'));
|
||||||
|
expect(metadataRows.length).toEqual(2);
|
||||||
|
|
||||||
|
const titleRow = metadataRows[0].queryAll(By.css('td'));
|
||||||
|
expect(titleRow.length).toEqual(3);
|
||||||
|
|
||||||
|
expect(titleRow[0].nativeElement.innerHTML).toContain('dc.title');
|
||||||
|
expect(titleRow[1].nativeElement.innerHTML).toContain('Mock item title');
|
||||||
|
expect(titleRow[2].nativeElement.innerHTML).toContain('en');
|
||||||
|
|
||||||
|
const authorRow = metadataRows[1].queryAll(By.css('td'));
|
||||||
|
expect(authorRow.length).toEqual(3);
|
||||||
|
|
||||||
|
expect(authorRow[0].nativeElement.innerHTML).toContain('dc.contributor.author');
|
||||||
|
expect(authorRow[1].nativeElement.innerHTML).toContain('Mayer, Ed');
|
||||||
|
expect(authorRow[2].nativeElement.innerHTML).toEqual('');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,20 @@
|
|||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {Metadatum} from '../../../core/shared/metadatum.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-modify-item-overview',
|
||||||
|
templateUrl: './modify-item-overview.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering a table containing the metadatavalues from the to be edited item
|
||||||
|
*/
|
||||||
|
export class ModifyItemOverviewComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() item: Item;
|
||||||
|
metadata: Metadatum[];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.metadata = this.item.metadata;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2>{{headerMessage | translate: {id: item.handle} }}</h2>
|
||||||
|
<p>{{descriptionMessage | translate}}</p>
|
||||||
|
<ds-modify-item-overview [item]="item"></ds-modify-item-overview>
|
||||||
|
<button (click)="performAction()" class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
|
||||||
|
</button>
|
||||||
|
<button [routerLink]="['/items/', item.id, 'edit']" class="btn btn-outline-secondary cancel">
|
||||||
|
{{cancelMessage| translate}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1,138 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {AbstractSimpleItemActionComponent} from './abstract-simple-item-action.component';
|
||||||
|
import {By} from '@angular/platform-browser';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {of as observableOf} from 'rxjs';
|
||||||
|
import {getItemEditPath} from '../../item-page-routing.module';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-simple-action',
|
||||||
|
templateUrl: './abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
export class MySimpleItemActionComponent extends AbstractSimpleItemActionComponent {
|
||||||
|
|
||||||
|
protected messageKey = 'myEditAction';
|
||||||
|
protected predicate = (rd: RemoteData<Item>) => rd.payload.isWithdrawn;
|
||||||
|
|
||||||
|
performAction() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let comp: MySimpleItemActionComponent;
|
||||||
|
let fixture: ComponentFixture<MySimpleItemActionComponent>;
|
||||||
|
|
||||||
|
let mockItem;
|
||||||
|
let itemPageUrl;
|
||||||
|
let routerStub;
|
||||||
|
let mockItemDataService;
|
||||||
|
let routeStub;
|
||||||
|
let notificationsServiceStub;
|
||||||
|
let successfulRestResponse;
|
||||||
|
let failRestResponse;
|
||||||
|
|
||||||
|
describe('AbstractSimpleItemActionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `${itemPageUrl}/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
mockItemDataService = jasmine.createSpyObj({
|
||||||
|
findById: observableOf(new RemoteData(false, false, true, undefined, mockItem))
|
||||||
|
});
|
||||||
|
|
||||||
|
routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: new RemoteData(false, false, true, null, {
|
||||||
|
id: 'fake-id'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
|
declarations: [MySimpleItemActionComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: Router, useValue: routerStub},
|
||||||
|
{provide: ItemDataService, useValue: mockItemDataService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsServiceStub},
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
successfulRestResponse = new RestResponse(true, '200');
|
||||||
|
failRestResponse = new RestResponse(false, '500');
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MySimpleItemActionComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a page with messages based on the provided messageKey', () => {
|
||||||
|
const header = fixture.debugElement.query(By.css('h2')).nativeElement;
|
||||||
|
expect(header.innerHTML).toContain('item.edit.myEditAction.header');
|
||||||
|
|
||||||
|
const description = fixture.debugElement.query(By.css('p')).nativeElement;
|
||||||
|
expect(description.innerHTML).toContain('item.edit.myEditAction.description');
|
||||||
|
|
||||||
|
const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement;
|
||||||
|
expect(confirmButton.innerHTML).toContain('item.edit.myEditAction.confirm');
|
||||||
|
|
||||||
|
const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement;
|
||||||
|
expect(cancelButton.innerHTML).toContain('item.edit.myEditAction.cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform action when the button is clicked', () => {
|
||||||
|
spyOn(comp, 'performAction');
|
||||||
|
const performButton = fixture.debugElement.query(By.css('.perform-action'));
|
||||||
|
performButton.triggerEventHandler('click', null);
|
||||||
|
|
||||||
|
expect(comp.performAction).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process a RestResponse to navigate and display success notification', () => {
|
||||||
|
spyOn(notificationsServiceStub, 'success');
|
||||||
|
comp.processRestResponse(successfulRestResponse);
|
||||||
|
|
||||||
|
expect(notificationsServiceStub.success).toHaveBeenCalled();
|
||||||
|
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditPath(mockItem.id)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process a RestResponse to navigate and display success notification', () => {
|
||||||
|
spyOn(notificationsServiceStub, 'error');
|
||||||
|
comp.processRestResponse(failRestResponse);
|
||||||
|
|
||||||
|
expect(notificationsServiceStub.error).toHaveBeenCalled();
|
||||||
|
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditPath(mockItem.id)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,84 @@
|
|||||||
|
import {Component, OnInit, Predicate} from '@angular/core';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {getSucceededRemoteData} from '../../../core/shared/operators';
|
||||||
|
import {first, map} from 'rxjs/operators';
|
||||||
|
import {RestResponse} from '../../../core/cache/response-cache.models';
|
||||||
|
import {findSuccessfulAccordingTo} from '../edit-item-operators';
|
||||||
|
import {getItemEditPath} from '../../item-page-routing.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render and handle simple item edit actions such as withdrawal and reinstatement.
|
||||||
|
* This component is not meant to be used itself but to be extended.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-simple-action',
|
||||||
|
templateUrl: './abstract-simple-item-action.component.html'
|
||||||
|
})
|
||||||
|
export class AbstractSimpleItemActionComponent implements OnInit {
|
||||||
|
|
||||||
|
itemRD$: Observable<RemoteData<Item>>;
|
||||||
|
item: Item;
|
||||||
|
|
||||||
|
protected messageKey: string;
|
||||||
|
confirmMessage: string;
|
||||||
|
cancelMessage: string;
|
||||||
|
headerMessage: string;
|
||||||
|
descriptionMessage: string;
|
||||||
|
|
||||||
|
protected predicate: Predicate<RemoteData<Item>>;
|
||||||
|
|
||||||
|
constructor(protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected itemDataService: ItemDataService,
|
||||||
|
protected translateService: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.itemRD$ = this.route.data.pipe(
|
||||||
|
map((data) => data.item),
|
||||||
|
getSucceededRemoteData()
|
||||||
|
)as Observable<RemoteData<Item>>;
|
||||||
|
|
||||||
|
this.itemRD$.pipe(first()).subscribe((rd) => {
|
||||||
|
this.item = rd.payload;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.confirmMessage = 'item.edit.' + this.messageKey + '.confirm';
|
||||||
|
this.cancelMessage = 'item.edit.' + this.messageKey + '.cancel';
|
||||||
|
this.headerMessage = 'item.edit.' + this.messageKey + '.header';
|
||||||
|
this.descriptionMessage = 'item.edit.' + this.messageKey + '.description';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the operation linked to this action
|
||||||
|
*/
|
||||||
|
performAction() {
|
||||||
|
// Overwrite in subclasses
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the response obtained during the performAction method and navigate back to the edit page
|
||||||
|
* @param response from the action in the performAction method
|
||||||
|
*/
|
||||||
|
processRestResponse(response: RestResponse) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
this.itemDataService.findById(this.item.id).pipe(
|
||||||
|
findSuccessfulAccordingTo(this.predicate)).subscribe(() => {
|
||||||
|
this.notificationsService.success(this.translateService.get('item.edit.' + this.messageKey + '.success'));
|
||||||
|
this.router.navigate([getItemEditPath(this.item.id)]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get('item.edit.' + this.messageKey + '.error'));
|
||||||
|
this.router.navigate([getItemEditPath(this.item.id)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -4,6 +4,18 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { ItemPageComponent } from './simple/item-page.component';
|
import { ItemPageComponent } from './simple/item-page.component';
|
||||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||||
import { ItemPageResolver } from './item-page.resolver';
|
import { ItemPageResolver } from './item-page.resolver';
|
||||||
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
|
import {URLCombiner} from '../core/url-combiner/url-combiner';
|
||||||
|
import {getItemModulePath} from '../app-routing.module';
|
||||||
|
|
||||||
|
export function getItemPageRoute(itemId: string) {
|
||||||
|
return new URLCombiner(getItemModulePath(), itemId).toString();
|
||||||
|
}
|
||||||
|
export function getItemEditPath(id: string) {
|
||||||
|
return new URLCombiner(getItemModulePath(),ITEM_EDIT_PATH.replace(/:id/, id)).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ITEM_EDIT_PATH = ':id/edit';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,6 +34,11 @@ import { ItemPageResolver } from './item-page.resolver';
|
|||||||
resolve: {
|
resolve: {
|
||||||
item: ItemPageResolver
|
item: ItemPageResolver
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_PATH,
|
||||||
|
loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule',
|
||||||
|
canActivate: [AuthenticatedGuard]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -18,11 +18,13 @@ import { FileSectionComponent } from './simple/field-components/file-section/fil
|
|||||||
import { CollectionsComponent } from './field-components/collections/collections.component';
|
import { CollectionsComponent } from './field-components/collections/collections.component';
|
||||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||||
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
||||||
|
import { EditItemPageModule } from './edit-item-page/edit-item-page.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
EditItemPageModule,
|
||||||
ItemPageRoutingModule
|
ItemPageRoutingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -3,6 +3,10 @@ import { RouterModule } from '@angular/router';
|
|||||||
|
|
||||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||||
|
|
||||||
|
const ITEM_MODULE_PATH = 'items';
|
||||||
|
export function getItemModulePath() {
|
||||||
|
return `/${ITEM_MODULE_PATH}`;
|
||||||
|
}
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forRoot([
|
RouterModule.forRoot([
|
||||||
@@ -10,7 +14,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
|||||||
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
||||||
{ path: 'communities', loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
{ path: 'communities', loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
||||||
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
|
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
|
||||||
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
{ path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
||||||
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
||||||
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
|
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
|
||||||
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
|
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
|
||||||
|
@@ -1,24 +1,44 @@
|
|||||||
import { Store } from '@ngrx/store';
|
import {Store} from '@ngrx/store';
|
||||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
import {cold, getTestScheduler} from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import {TestScheduler} from 'rxjs/testing';
|
||||||
import { BrowseService } from '../browse/browse.service';
|
import {BrowseService} from '../browse/browse.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import {RemoteDataBuildService} from '../cache/builders/remote-data-build.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import {ResponseCacheService} from '../cache/response-cache.service';
|
||||||
import { CoreState } from '../core.reducers';
|
import {CoreState} from '../core.reducers';
|
||||||
import { ItemDataService } from './item-data.service';
|
import {ItemDataService} from './item-data.service';
|
||||||
import { RequestService } from './request.service';
|
import {RequestService} from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import {HALEndpointService} from '../shared/hal-endpoint.service';
|
||||||
import { FindAllOptions } from './request.models';
|
import {FindAllOptions, RestRequest} from './request.models';
|
||||||
|
import {Observable, of as observableOf} from 'rxjs';
|
||||||
|
import {ResponseCacheEntry} from '../cache/response-cache.reducer';
|
||||||
|
import {RestResponse} from '../cache/response-cache.models';
|
||||||
|
|
||||||
describe('ItemDataService', () => {
|
describe('ItemDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
let service: ItemDataService;
|
let service: ItemDataService;
|
||||||
let bs: BrowseService;
|
let bs: BrowseService;
|
||||||
const requestService = {} as RequestService;
|
const requestService = {
|
||||||
const responseCache = {} as ResponseCacheService;
|
generateRequestId(): string {
|
||||||
|
return scopeID;
|
||||||
|
},
|
||||||
|
configure(request: RestRequest) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
} as RequestService;
|
||||||
|
const responseCache = {
|
||||||
|
get(href: string) {
|
||||||
|
const responseCacheEntry = new ResponseCacheEntry();
|
||||||
|
responseCacheEntry.response = new RestResponse(true, '200');
|
||||||
|
return observableOf(responseCacheEntry);
|
||||||
|
}
|
||||||
|
} as ResponseCacheService;
|
||||||
const rdbService = {} as RemoteDataBuildService;
|
const rdbService = {} as RemoteDataBuildService;
|
||||||
const store = {} as Store<CoreState>;
|
const store = {} as Store<CoreState>;
|
||||||
const halEndpointService = {} as HALEndpointService;
|
const halEndpointService = {
|
||||||
|
getEndpoint(linkPath: string): Observable<string> {
|
||||||
|
return cold('a', {a: itemEndpoint});
|
||||||
|
}
|
||||||
|
} as HALEndpointService;
|
||||||
|
|
||||||
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
|
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
|
||||||
const options = Object.assign(new FindAllOptions(), {
|
const options = Object.assign(new FindAllOptions(), {
|
||||||
@@ -34,10 +54,12 @@ describe('ItemDataService', () => {
|
|||||||
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
|
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
|
||||||
const serviceEndpoint = `https://rest.api/core/items`;
|
const serviceEndpoint = `https://rest.api/core/items`;
|
||||||
const browseError = new Error('getBrowseURL failed');
|
const browseError = new Error('getBrowseURL failed');
|
||||||
|
const itemEndpoint = 'https://rest.api/core/items';
|
||||||
|
const ScopedItemEndpoint = `https://rest.api/core/items/${scopeID}`;
|
||||||
|
|
||||||
function initMockBrowseService(isSuccessful: boolean) {
|
function initMockBrowseService(isSuccessful: boolean) {
|
||||||
const obs = isSuccessful ?
|
const obs = isSuccessful ?
|
||||||
cold('--a-', { a: itemBrowseEndpoint }) :
|
cold('--a-', {a: itemBrowseEndpoint}) :
|
||||||
cold('--#-', undefined, browseError);
|
cold('--#-', undefined, browseError);
|
||||||
return jasmine.createSpyObj('bs', {
|
return jasmine.createSpyObj('bs', {
|
||||||
getBrowseURLFor: obs
|
getBrowseURLFor: obs
|
||||||
@@ -65,7 +87,7 @@ describe('ItemDataService', () => {
|
|||||||
service = initTestService();
|
service = initTestService();
|
||||||
|
|
||||||
const result = service.getBrowseEndpoint(options);
|
const result = service.getBrowseEndpoint(options);
|
||||||
const expected = cold('--b-', { b: scopedEndpoint });
|
const expected = cold('--b-', {b: scopedEndpoint});
|
||||||
|
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
@@ -83,4 +105,70 @@ describe('ItemDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getItemWithdrawEndpoint', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
service = initTestService();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the endpoint to withdraw and reinstate items', () => {
|
||||||
|
const result = service.getItemWithdrawEndpoint(scopeID);
|
||||||
|
const expected = cold('a', {a: ScopedItemEndpoint});
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setWithDrawn', () => {
|
||||||
|
const expected = new RestResponse(true, '200');
|
||||||
|
const result = service.setWithDrawn(scopeID, true);
|
||||||
|
result.subscribe((v) => expect(v).toEqual(expected));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getItemDiscoverableEndpoint', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
service = initTestService();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the endpoint to make an item private or public', () => {
|
||||||
|
const result = service.getItemDiscoverableEndpoint(scopeID);
|
||||||
|
const expected = cold('a', {a: ScopedItemEndpoint});
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setDiscoverable', () => {
|
||||||
|
const expected = new RestResponse(true, '200');
|
||||||
|
const result = service.setDiscoverable(scopeID, false);
|
||||||
|
result.subscribe((v) => expect(v).toEqual(expected));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getItemDeleteEndpoint', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
service = initTestService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the endpoint to make an item private or public', () => {
|
||||||
|
const result = service.getItemDeleteEndpoint(scopeID);
|
||||||
|
const expected = cold('a', {a: ScopedItemEndpoint});
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete the item', () => {
|
||||||
|
const expected = new RestResponse(true, '200');
|
||||||
|
const result = service.delete(scopeID);
|
||||||
|
result.subscribe((v) => expect(v).toEqual(expected));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,21 +1,22 @@
|
|||||||
|
import {distinctUntilChanged, filter, map} from 'rxjs/operators';
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Store} from '@ngrx/store';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {isNotEmpty} from '../../shared/empty.util';
|
||||||
|
import {BrowseService} from '../browse/browse.service';
|
||||||
|
import {RemoteDataBuildService} from '../cache/builders/remote-data-build.service';
|
||||||
|
import {NormalizedItem} from '../cache/models/normalized-item.model';
|
||||||
|
import {ResponseCacheService} from '../cache/response-cache.service';
|
||||||
|
import {CoreState} from '../core.reducers';
|
||||||
|
import {Item} from '../shared/item.model';
|
||||||
|
import {URLCombiner} from '../url-combiner/url-combiner';
|
||||||
|
|
||||||
import {distinctUntilChanged, map, filter} from 'rxjs/operators';
|
import {DataService} from './data.service';
|
||||||
import { Injectable } from '@angular/core';
|
import {RequestService} from './request.service';
|
||||||
import { Store } from '@ngrx/store';
|
import {HALEndpointService} from '../shared/hal-endpoint.service';
|
||||||
import { Observable } from 'rxjs';
|
import {DeleteRequest, FindAllOptions, PatchRequest, RestRequest} from './request.models';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import {configureRequest, getResponseFromSelflink} from '../shared/operators';
|
||||||
import { BrowseService } from '../browse/browse.service';
|
import {ResponseCacheEntry} from '../cache/response-cache.reducer';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { NormalizedItem } from '../cache/models/normalized-item.model';
|
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { Item } from '../shared/item.model';
|
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
|
||||||
|
|
||||||
import { DataService } from './data.service';
|
|
||||||
import { RequestService } from './request.service';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|
||||||
import { FindAllOptions } from './request.models';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||||
@@ -48,4 +49,93 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
|
|||||||
distinctUntilChanged(),);
|
distinctUntilChanged(),);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint for item withdrawal and reinstatement
|
||||||
|
* @param itemId
|
||||||
|
*/
|
||||||
|
public getItemWithdrawEndpoint(itemId: string): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
|
map((endpoint: string) => this.getFindByIDHref(endpoint, itemId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint to make item private and public
|
||||||
|
* @param itemId
|
||||||
|
*/
|
||||||
|
public getItemDiscoverableEndpoint(itemId: string): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
|
map((endpoint: string) => this.getFindByIDHref(endpoint, itemId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint to delete the item
|
||||||
|
* @param itemId
|
||||||
|
*/
|
||||||
|
public getItemDeleteEndpoint(itemId: string): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
|
map((endpoint: string) => this.getFindByIDHref(endpoint, itemId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the isWithdrawn state of an item to a specified state
|
||||||
|
* @param itemId
|
||||||
|
* @param withdrawn
|
||||||
|
*/
|
||||||
|
public setWithDrawn(itemId: string, withdrawn: boolean) {
|
||||||
|
const patchOperation = [{
|
||||||
|
op: 'replace', path: '/withdrawn', value: withdrawn
|
||||||
|
}];
|
||||||
|
return this.getItemWithdrawEndpoint(itemId).pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((endpointURL: string) =>
|
||||||
|
new PatchRequest(this.requestService.generateRequestId(), endpointURL, patchOperation)
|
||||||
|
),
|
||||||
|
configureRequest(this.requestService),
|
||||||
|
map((request: RestRequest) => request.href),
|
||||||
|
getResponseFromSelflink(this.responseCache),
|
||||||
|
map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the isDiscoverable state of an item to a specified state
|
||||||
|
* @param itemId
|
||||||
|
* @param discoverable
|
||||||
|
*/
|
||||||
|
public setDiscoverable(itemId: string, discoverable: boolean) {
|
||||||
|
const patchOperation = [{
|
||||||
|
op: 'replace', path: '/discoverable', value: discoverable
|
||||||
|
}];
|
||||||
|
return this.getItemDiscoverableEndpoint(itemId).pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((endpointURL: string) =>
|
||||||
|
new PatchRequest(this.requestService.generateRequestId(), endpointURL, patchOperation)
|
||||||
|
),
|
||||||
|
configureRequest(this.requestService),
|
||||||
|
map((request: RestRequest) => request.href),
|
||||||
|
getResponseFromSelflink(this.responseCache),
|
||||||
|
map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the item
|
||||||
|
* @param itemId
|
||||||
|
*/
|
||||||
|
public delete(itemId: string) {
|
||||||
|
return this.getItemDeleteEndpoint(itemId).pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((endpointURL: string) =>
|
||||||
|
new DeleteRequest(this.requestService.generateRequestId(), endpointURL)
|
||||||
|
),
|
||||||
|
configureRequest(this.requestService),
|
||||||
|
map((request: RestRequest) => request.href),
|
||||||
|
getResponseFromSelflink(this.responseCache),
|
||||||
|
map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,23 @@
|
|||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import {cold, getTestScheduler, hot} from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import {TestScheduler} from 'rxjs/testing';
|
||||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
import {getMockRequestService} from '../../shared/mocks/mock-request.service';
|
||||||
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
|
import {getMockResponseCacheService} from '../../shared/mocks/mock-response-cache.service';
|
||||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
import {ResponseCacheEntry} from '../cache/response-cache.reducer';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import {ResponseCacheService} from '../cache/response-cache.service';
|
||||||
import { GetRequest, RestRequest } from '../data/request.models';
|
import {GetRequest} from '../data/request.models';
|
||||||
import { RequestEntry } from '../data/request.reducer';
|
import {RequestEntry} from '../data/request.reducer';
|
||||||
import { RequestService } from '../data/request.service';
|
import {RequestService} from '../data/request.service';
|
||||||
import {
|
import {
|
||||||
configureRequest,
|
configureRequest,
|
||||||
filterSuccessfulResponses, getRemoteDataPayload,
|
filterSuccessfulResponses,
|
||||||
getRequestFromSelflink, getResourceLinksFromResponse,
|
getAllSucceededRemoteData,
|
||||||
getResponseFromSelflink
|
getRemoteDataPayload,
|
||||||
|
getRequestFromSelflink,
|
||||||
|
getResourceLinksFromResponse,
|
||||||
|
getResponseFromSelflink,
|
||||||
|
getSucceededRemoteData
|
||||||
} from './operators';
|
} from './operators';
|
||||||
|
import {RemoteData} from '../data/remote-data';
|
||||||
|
|
||||||
describe('Core Module - RxJS Operators', () => {
|
describe('Core Module - RxJS Operators', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -20,11 +25,11 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
const testSelfLink = 'https://rest.api/';
|
const testSelfLink = 'https://rest.api/';
|
||||||
|
|
||||||
const testRCEs = {
|
const testRCEs = {
|
||||||
a: { response: { isSuccessful: true, resourceSelfLinks: ['a', 'b', 'c', 'd'] } },
|
a: {response: {isSuccessful: true, resourceSelfLinks: ['a', 'b', 'c', 'd']}},
|
||||||
b: { response: { isSuccessful: false, resourceSelfLinks: ['e', 'f'] } },
|
b: {response: {isSuccessful: false, resourceSelfLinks: ['e', 'f']}},
|
||||||
c: { response: { isSuccessful: undefined, resourceSelfLinks: ['g', 'h', 'i'] } },
|
c: {response: {isSuccessful: undefined, resourceSelfLinks: ['g', 'h', 'i']}},
|
||||||
d: { response: { isSuccessful: true, resourceSelfLinks: ['j', 'k', 'l', 'm', 'n'] } },
|
d: {response: {isSuccessful: true, resourceSelfLinks: ['j', 'k', 'l', 'm', 'n']}},
|
||||||
e: { response: { isSuccessful: 1, resourceSelfLinks: [] } }
|
e: {response: {isSuccessful: 1, resourceSelfLinks: []}}
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -36,31 +41,31 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
it('should return the RequestEntry corresponding to the self link in the source', () => {
|
it('should return the RequestEntry corresponding to the self link in the source', () => {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
|
|
||||||
const source = hot('a', { a: testSelfLink });
|
const source = hot('a', {a: testSelfLink});
|
||||||
const result = source.pipe(getRequestFromSelflink(requestService));
|
const result = source.pipe(getRequestFromSelflink(requestService));
|
||||||
const expected = cold('a', { a: new RequestEntry()});
|
const expected = cold('a', {a: new RequestEntry()});
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the requestService to fetch the request by its self link', () => {
|
it('should use the requestService to fetch the request by its self link', () => {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
|
|
||||||
const source = hot('a', { a: testSelfLink });
|
const source = hot('a', {a: testSelfLink});
|
||||||
scheduler.schedule(() => source.pipe(getRequestFromSelflink(requestService)).subscribe());
|
scheduler.schedule(() => source.pipe(getRequestFromSelflink(requestService)).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(requestService.getByHref).toHaveBeenCalledWith(testSelfLink)
|
expect(requestService.getByHref).toHaveBeenCalledWith(testSelfLink);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shouldn\'t return anything if there is no request matching the self link', () => {
|
it('shouldn\'t return anything if there is no request matching the self link', () => {
|
||||||
requestService = getMockRequestService(cold('a', { a: undefined }));
|
requestService = getMockRequestService(cold('a', {a: undefined}));
|
||||||
|
|
||||||
const source = hot('a', { a: testSelfLink });
|
const source = hot('a', {a: testSelfLink});
|
||||||
const result = source.pipe(getRequestFromSelflink(requestService));
|
const result = source.pipe(getRequestFromSelflink(requestService));
|
||||||
const expected = cold('-');
|
const expected = cold('-');
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,31 +79,31 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
it('should return the ResponseCacheEntry corresponding to the self link in the source', () => {
|
it('should return the ResponseCacheEntry corresponding to the self link in the source', () => {
|
||||||
responseCacheService = getMockResponseCacheService();
|
responseCacheService = getMockResponseCacheService();
|
||||||
|
|
||||||
const source = hot('a', { a: testSelfLink });
|
const source = hot('a', {a: testSelfLink});
|
||||||
const result = source.pipe(getResponseFromSelflink(responseCacheService));
|
const result = source.pipe(getResponseFromSelflink(responseCacheService));
|
||||||
const expected = cold('a', { a: new ResponseCacheEntry()});
|
const expected = cold('a', {a: new ResponseCacheEntry()});
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the responseCacheService to fetch the response by the request\'s link', () => {
|
it('should use the responseCacheService to fetch the response by the request\'s link', () => {
|
||||||
responseCacheService = getMockResponseCacheService();
|
responseCacheService = getMockResponseCacheService();
|
||||||
|
|
||||||
const source = hot('a', { a: testSelfLink });
|
const source = hot('a', {a: testSelfLink});
|
||||||
scheduler.schedule(() => source.pipe(getResponseFromSelflink(responseCacheService)).subscribe());
|
scheduler.schedule(() => source.pipe(getResponseFromSelflink(responseCacheService)).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(responseCacheService.get).toHaveBeenCalledWith(testSelfLink)
|
expect(responseCacheService.get).toHaveBeenCalledWith(testSelfLink);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shouldn\'t return anything if there is no response matching the request\'s link', () => {
|
it('shouldn\'t return anything if there is no response matching the request\'s link', () => {
|
||||||
responseCacheService = getMockResponseCacheService(undefined, cold('a', { a: undefined }));
|
responseCacheService = getMockResponseCacheService(undefined, cold('a', {a: undefined}));
|
||||||
|
|
||||||
const source = hot('a', { a: testSelfLink });
|
const source = hot('a', {a: testSelfLink});
|
||||||
const result = source.pipe(getResponseFromSelflink(responseCacheService));
|
const result = source.pipe(getResponseFromSelflink(responseCacheService));
|
||||||
const expected = cold('-');
|
const expected = cold('-');
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,7 +113,7 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
const result = source.pipe(filterSuccessfulResponses());
|
const result = source.pipe(filterSuccessfulResponses());
|
||||||
const expected = cold('a--d-', testRCEs);
|
const expected = cold('a--d-', testRCEs);
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,7 +126,7 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
d: testRCEs.d.response.resourceSelfLinks
|
d: testRCEs.d.response.resourceSelfLinks
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,24 +134,61 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
it('should call requestService.configure with the source request', () => {
|
it('should call requestService.configure with the source request', () => {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
const testRequest = new GetRequest('6b789e31-f026-4ff8-8993-4eb3b730c841', testSelfLink);
|
const testRequest = new GetRequest('6b789e31-f026-4ff8-8993-4eb3b730c841', testSelfLink);
|
||||||
const source = hot('a', { a: testRequest });
|
const source = hot('a', {a: testRequest});
|
||||||
scheduler.schedule(() => source.pipe(configureRequest(requestService)).subscribe());
|
scheduler.schedule(() => source.pipe(configureRequest(requestService)).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(requestService.configure).toHaveBeenCalledWith(testRequest)
|
expect(requestService.configure).toHaveBeenCalledWith(testRequest);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getRemoteDataPayload', () => {
|
describe('getRemoteDataPayload', () => {
|
||||||
it('should return the payload of the source RemoteData', () => {
|
it('should return the payload of the source RemoteData', () => {
|
||||||
const testRD = { a: { payload: 'a' } };
|
const testRD = {a: {payload: 'a'}};
|
||||||
const source = hot('a', testRD);
|
const source = hot('a', testRD);
|
||||||
const result = source.pipe(getRemoteDataPayload());
|
const result = source.pipe(getRemoteDataPayload());
|
||||||
const expected = cold('a', {
|
const expected = cold('a', {
|
||||||
a: testRD.a.payload,
|
a: testRD.a.payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeObservable(expected)
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getSucceededRemoteData', () => {
|
||||||
|
it('should return the first() hasSucceeded RemoteData Observable', () => {
|
||||||
|
const testRD = {
|
||||||
|
a: new RemoteData(false, false, true, null, undefined),
|
||||||
|
b: new RemoteData(false, false, false, null, 'b'),
|
||||||
|
c: new RemoteData(false, false, undefined, null, 'c'),
|
||||||
|
d: new RemoteData(false, false, true, null, 'd'),
|
||||||
|
e: new RemoteData(false, false, true, null, 'e'),
|
||||||
|
};
|
||||||
|
const source = hot('abcde', testRD);
|
||||||
|
const result = source.pipe(getSucceededRemoteData());
|
||||||
|
|
||||||
|
result.subscribe((value) => expect(value)
|
||||||
|
.toEqual(new RemoteData(false, false, true, null, 'd')));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('getAllSucceededRemoteData', () => {
|
||||||
|
it('should return all hasSucceeded RemoteData Observables', () => {
|
||||||
|
const testRD = {
|
||||||
|
a: new RemoteData(false, false, true, null, undefined),
|
||||||
|
b: new RemoteData(false, false, false, null, 'b'),
|
||||||
|
c: new RemoteData(false, false, undefined, null, 'c'),
|
||||||
|
d: new RemoteData(false, false, true, null, 'd'),
|
||||||
|
e: new RemoteData(false, false, true, null, 'e'),
|
||||||
|
};
|
||||||
|
const source = hot('abcde', testRD);
|
||||||
|
const result = source.pipe(getAllSucceededRemoteData());
|
||||||
|
const expected = cold('---de', testRD);
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -54,12 +54,16 @@ export const getSucceededRemoteData = () =>
|
|||||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
source.pipe(first((rd: RemoteData<T>) => rd.hasSucceeded));
|
source.pipe(first((rd: RemoteData<T>) => rd.hasSucceeded));
|
||||||
|
|
||||||
|
export const getAllSucceededRemoteData = () =>
|
||||||
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
|
source.pipe(filter((rd: RemoteData<T>) => rd.hasSucceeded));
|
||||||
|
|
||||||
export const toDSpaceObjectListRD = () =>
|
export const toDSpaceObjectListRD = () =>
|
||||||
<T extends DSpaceObject>(source: Observable<RemoteData<PaginatedList<SearchResult<T>>>>): Observable<RemoteData<PaginatedList<T>>> =>
|
<T extends DSpaceObject>(source: Observable<RemoteData<PaginatedList<SearchResult<T>>>>): Observable<RemoteData<PaginatedList<T>>> =>
|
||||||
source.pipe(
|
source.pipe(
|
||||||
map((rd: RemoteData<PaginatedList<SearchResult<T>>>) => {
|
map((rd: RemoteData<PaginatedList<SearchResult<T>>>) => {
|
||||||
const dsoPage: T[] = rd.payload.page.map((searchResult: SearchResult<T>) => searchResult.dspaceObject);
|
const dsoPage: T[] = rd.payload.page.map((searchResult: SearchResult<T>) => searchResult.dspaceObject);
|
||||||
const payload = Object.assign(rd.payload, { page: dsoPage }) as any;
|
const payload = Object.assign(rd.payload, {page: dsoPage}) as any;
|
||||||
return Object.assign(rd, {payload: payload});
|
return Object.assign(rd, {payload: payload});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
|
||||||
import { QueryParamsDirectiveStub } from './query-params-directive-stub';
|
import { QueryParamsDirectiveStub } from './query-params-directive-stub';
|
||||||
|
import { MySimpleItemActionComponent } from '../../+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {SharedModule} from '../shared.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module isn't used. It serves to prevent the AoT compiler
|
* This module isn't used. It serves to prevent the AoT compiler
|
||||||
@@ -8,8 +11,15 @@ import { QueryParamsDirectiveStub } from './query-params-directive-stub';
|
|||||||
* See https://github.com/angular/angular/issues/13590
|
* See https://github.com/angular/angular/issues/13590
|
||||||
*/
|
*/
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
QueryParamsDirectiveStub
|
QueryParamsDirectiveStub,
|
||||||
|
MySimpleItemActionComponent
|
||||||
|
], schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class TestModule {}
|
export class TestModule {}
|
||||||
|
Reference in New Issue
Block a user