mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
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",
|
||||
"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": {
|
||||
|
@@ -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 { FullItemPageComponent } from './full/full-item-page.component';
|
||||
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({
|
||||
imports: [
|
||||
@@ -22,6 +34,11 @@ import { ItemPageResolver } from './item-page.resolver';
|
||||
resolve: {
|
||||
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 { FullItemPageComponent } from './full/full-item-page.component';
|
||||
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
||||
import { EditItemPageModule } from './edit-item-page/edit-item-page.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
EditItemPageModule,
|
||||
ItemPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -3,6 +3,10 @@ import { RouterModule } from '@angular/router';
|
||||
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
|
||||
const ITEM_MODULE_PATH = 'items';
|
||||
export function getItemModulePath() {
|
||||
return `/${ITEM_MODULE_PATH}`;
|
||||
}
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot([
|
||||
@@ -10,7 +14,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
||||
{ path: 'communities', loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
||||
{ 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: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
|
||||
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
|
||||
|
@@ -1,24 +1,44 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { BrowseService } from '../browse/browse.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { ItemDataService } from './item-data.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { FindAllOptions } from './request.models';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {cold, getTestScheduler} from 'jasmine-marbles';
|
||||
import {TestScheduler} from 'rxjs/testing';
|
||||
import {BrowseService} from '../browse/browse.service';
|
||||
import {RemoteDataBuildService} from '../cache/builders/remote-data-build.service';
|
||||
import {ResponseCacheService} from '../cache/response-cache.service';
|
||||
import {CoreState} from '../core.reducers';
|
||||
import {ItemDataService} from './item-data.service';
|
||||
import {RequestService} from './request.service';
|
||||
import {HALEndpointService} from '../shared/hal-endpoint.service';
|
||||
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', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: ItemDataService;
|
||||
let bs: BrowseService;
|
||||
const requestService = {} as RequestService;
|
||||
const responseCache = {} as ResponseCacheService;
|
||||
const requestService = {
|
||||
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 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 options = Object.assign(new FindAllOptions(), {
|
||||
@@ -34,10 +54,12 @@ describe('ItemDataService', () => {
|
||||
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
|
||||
const serviceEndpoint = `https://rest.api/core/items`;
|
||||
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) {
|
||||
const obs = isSuccessful ?
|
||||
cold('--a-', { a: itemBrowseEndpoint }) :
|
||||
cold('--a-', {a: itemBrowseEndpoint}) :
|
||||
cold('--#-', undefined, browseError);
|
||||
return jasmine.createSpyObj('bs', {
|
||||
getBrowseURLFor: obs
|
||||
@@ -65,7 +87,7 @@ describe('ItemDataService', () => {
|
||||
service = initTestService();
|
||||
|
||||
const result = service.getBrowseEndpoint(options);
|
||||
const expected = cold('--b-', { b: scopedEndpoint });
|
||||
const expected = cold('--b-', {b: scopedEndpoint});
|
||||
|
||||
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 { 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 { DataService } from './data.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { FindAllOptions } from './request.models';
|
||||
import {DataService} from './data.service';
|
||||
import {RequestService} from './request.service';
|
||||
import {HALEndpointService} from '../shared/hal-endpoint.service';
|
||||
import {DeleteRequest, FindAllOptions, PatchRequest, RestRequest} from './request.models';
|
||||
import {configureRequest, getResponseFromSelflink} from '../shared/operators';
|
||||
import {ResponseCacheEntry} from '../cache/response-cache.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
@@ -48,4 +49,93 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
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 { TestScheduler } from 'rxjs/testing';
|
||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { GetRequest, RestRequest } from '../data/request.models';
|
||||
import { RequestEntry } from '../data/request.reducer';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import {cold, getTestScheduler, hot} from 'jasmine-marbles';
|
||||
import {TestScheduler} from 'rxjs/testing';
|
||||
import {getMockRequestService} from '../../shared/mocks/mock-request.service';
|
||||
import {getMockResponseCacheService} from '../../shared/mocks/mock-response-cache.service';
|
||||
import {ResponseCacheEntry} from '../cache/response-cache.reducer';
|
||||
import {ResponseCacheService} from '../cache/response-cache.service';
|
||||
import {GetRequest} from '../data/request.models';
|
||||
import {RequestEntry} from '../data/request.reducer';
|
||||
import {RequestService} from '../data/request.service';
|
||||
import {
|
||||
configureRequest,
|
||||
filterSuccessfulResponses, getRemoteDataPayload,
|
||||
getRequestFromSelflink, getResourceLinksFromResponse,
|
||||
getResponseFromSelflink
|
||||
filterSuccessfulResponses,
|
||||
getAllSucceededRemoteData,
|
||||
getRemoteDataPayload,
|
||||
getRequestFromSelflink,
|
||||
getResourceLinksFromResponse,
|
||||
getResponseFromSelflink,
|
||||
getSucceededRemoteData
|
||||
} from './operators';
|
||||
import {RemoteData} from '../data/remote-data';
|
||||
|
||||
describe('Core Module - RxJS Operators', () => {
|
||||
let scheduler: TestScheduler;
|
||||
@@ -20,11 +25,11 @@ describe('Core Module - RxJS Operators', () => {
|
||||
const testSelfLink = 'https://rest.api/';
|
||||
|
||||
const testRCEs = {
|
||||
a: { response: { isSuccessful: true, resourceSelfLinks: ['a', 'b', 'c', 'd'] } },
|
||||
b: { response: { isSuccessful: false, resourceSelfLinks: ['e', 'f'] } },
|
||||
c: { response: { isSuccessful: undefined, resourceSelfLinks: ['g', 'h', 'i'] } },
|
||||
d: { response: { isSuccessful: true, resourceSelfLinks: ['j', 'k', 'l', 'm', 'n'] } },
|
||||
e: { response: { isSuccessful: 1, resourceSelfLinks: [] } }
|
||||
a: {response: {isSuccessful: true, resourceSelfLinks: ['a', 'b', 'c', 'd']}},
|
||||
b: {response: {isSuccessful: false, resourceSelfLinks: ['e', 'f']}},
|
||||
c: {response: {isSuccessful: undefined, resourceSelfLinks: ['g', 'h', 'i']}},
|
||||
d: {response: {isSuccessful: true, resourceSelfLinks: ['j', 'k', 'l', 'm', 'n']}},
|
||||
e: {response: {isSuccessful: 1, resourceSelfLinks: []}}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -36,31 +41,31 @@ describe('Core Module - RxJS Operators', () => {
|
||||
it('should return the RequestEntry corresponding to the self link in the source', () => {
|
||||
requestService = getMockRequestService();
|
||||
|
||||
const source = hot('a', { a: testSelfLink });
|
||||
const source = hot('a', {a: testSelfLink});
|
||||
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', () => {
|
||||
requestService = getMockRequestService();
|
||||
|
||||
const source = hot('a', { a: testSelfLink });
|
||||
const source = hot('a', {a: testSelfLink});
|
||||
scheduler.schedule(() => source.pipe(getRequestFromSelflink(requestService)).subscribe());
|
||||
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', () => {
|
||||
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 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', () => {
|
||||
responseCacheService = getMockResponseCacheService();
|
||||
|
||||
const source = hot('a', { a: testSelfLink });
|
||||
const source = hot('a', {a: testSelfLink});
|
||||
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', () => {
|
||||
responseCacheService = getMockResponseCacheService();
|
||||
|
||||
const source = hot('a', { a: testSelfLink });
|
||||
const source = hot('a', {a: testSelfLink});
|
||||
scheduler.schedule(() => source.pipe(getResponseFromSelflink(responseCacheService)).subscribe());
|
||||
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', () => {
|
||||
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 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 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
|
||||
});
|
||||
|
||||
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', () => {
|
||||
requestService = getMockRequestService();
|
||||
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.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(testRequest)
|
||||
expect(requestService.configure).toHaveBeenCalledWith(testRequest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRemoteDataPayload', () => {
|
||||
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 result = source.pipe(getRemoteDataPayload());
|
||||
const expected = cold('a', {
|
||||
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>> =>
|
||||
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 = () =>
|
||||
<T extends DSpaceObject>(source: Observable<RemoteData<PaginatedList<SearchResult<T>>>>): Observable<RemoteData<PaginatedList<T>>> =>
|
||||
source.pipe(
|
||||
map((rd: RemoteData<PaginatedList<SearchResult<T>>>) => {
|
||||
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});
|
||||
})
|
||||
);
|
||||
|
@@ -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 { 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
|
||||
@@ -8,8 +11,15 @@ import { QueryParamsDirectiveStub } from './query-params-directive-stub';
|
||||
* See https://github.com/angular/angular/issues/13590
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
QueryParamsDirectiveStub
|
||||
QueryParamsDirectiveStub,
|
||||
MySimpleItemActionComponent
|
||||
], schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
})
|
||||
export class TestModule {}
|
||||
|
Reference in New Issue
Block a user