--- /dev/null
+import { Component, Input } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { ActivatedRoute, ParamMap } from '@angular/router';
+import { IdlObject, IdlService } from '@eg/core/idl.service';
+import { of } from 'rxjs';
+import { BasicAdminPageComponent } from './basic-admin-page.component';
+
+@Component({
+ selector: 'eg-title',
+ template: ''
+})
+class MockTitleComponent {
+ @Input() prefix: string;
+}
+
+@Component({
+ selector: 'eg-staff-banner',
+ template: ''
+})
+class MockStaffBannerComponent {
+ @Input() bannerText: string;
+}
+
+@Component({
+ selector: 'eg-admin-page',
+ template: ''
+})
+class MockAdminPageComponent {
+ @Input() configLinkBasePath: string;
+ @Input() defaultNewRecord: IdlObject;
+ @Input() disableOrgFilter: boolean;
+ @Input() fieldOrder: string;
+ @Input() idlClass: string;
+ @Input() persistKeyPfx: string;
+ @Input() readonlyFields: string;
+ @Input() enableUndelete: boolean;
+}
+
+describe('Component: BasicAdminPage', () => {
+ let component: BasicAdminPageComponent;
+ let fixture: ComponentFixture<BasicAdminPageComponent>;
+ let idlServiceStub: Partial<IdlService>;
+ let routeStub: any;
+
+ beforeEach(() => {
+ idlServiceStub = {
+ create: (cls: string, seed?: []) => {
+ return {
+ a: seed || [],
+ classname: cls,
+ _isfieldmapper: true,
+
+ field1(value: any): any {
+ this.a[0] = value;
+ return this.a[0];
+ }
+ };
+ },
+ classes: [{ tbl1: { table: 'schema1.table1' } }]
+ };
+
+ const emptyParamMap: ParamMap = {
+ has: (name: string) => false,
+ get: (name: string) => null,
+ getAll: (name: string) => [],
+ keys: []
+ };
+ const data = [{
+ schema: 'schema1',
+ table: 'table1',
+ defaultNewRecord: { field1: 'value1' },
+ enableUndelete: true
+ }];
+ const parentRoute = { url: of('') };
+ const snapshot = { parent: { url: [{ path: '' }] } };
+ routeStub = {
+ paramMap: of(emptyParamMap),
+ data: of(data),
+ parent: parentRoute,
+ snapshot
+ };
+
+ TestBed.configureTestingModule({
+ imports: [],
+ providers: [
+ { provide: IdlService, useValue: idlServiceStub },
+ { provide: ActivatedRoute, useValue: routeStub }
+ ],
+ declarations: [
+ BasicAdminPageComponent,
+ MockTitleComponent,
+ MockStaffBannerComponent,
+ MockAdminPageComponent
+ ]
+ });
+ fixture = TestBed.createComponent(BasicAdminPageComponent);
+ component = fixture.componentInstance;
+ component.idlClass = 'tbl1';
+ fixture.detectChanges();
+ });
+
+ it('sets default new record from routing data', () => {
+ const adminPage: MockAdminPageComponent = fixture.debugElement.query(
+ By.directive(MockAdminPageComponent)).componentInstance;
+ expect(adminPage.defaultNewRecord.a[0]).toEqual('value1');
+ });
+ it('sets enableUndelete from routing data', () => {
+ const adminPage: MockAdminPageComponent = fixture.debugElement.query(
+ By.directive(MockAdminPageComponent)).componentInstance;
+ expect(adminPage.enableUndelete).toEqual(true);
+ });
+});
configLinkBasePath="{{configLinkBasePath}}"
fieldOrder="{{fieldOrder}}"
readonlyFields="{{readonlyFields}}"
+ [enableUndelete]="enableUndelete"
[disableOrgFilter]="disableOrgFilter"></eg-admin-page>
</ng-container>
`
// Tell the admin page to disable and hide the automagic org unit filter
disableOrgFilter: boolean;
+ enableUndelete: boolean;
+
private getParams$: Observable<ParamMap>;
private getRouteData$: Observable<any>;
private getParentUrl$: Observable<any>;
if (!this.table) {
this.table = data['table'];
}
- this.disableOrgFilter = data['disableOrgFilter'];
- this.fieldOrder = data['fieldOrder'];
- this.readonlyFields = data['readonlyFields'];
+ this.disableOrgFilter = data['disableOrgFilter'];
+ this.enableUndelete = data['enableUndelete'];
+ this.fieldOrder = data['fieldOrder'];
+ this.readonlyFields = data['readonlyFields'];
}
}));
data: [{
schema: 'asset',
table: 'copy_location',
+ enableUndelete: true,
readonlyFields: 'deleted',
fieldOrder: 'owning_lib,name,opac_visible,circulate,holdable,hold_verify,checkin_alert,deleted,label_prefix,label_suffix,url,id'}]
}, {
<ng-template #deleteSuccessStrTmpl i18n>{{idlClassDef.label}} Successfully Deleted</ng-template>
<eg-string #deleteSuccessString [template]="deleteSuccessStrTmpl"></eg-string>
+<ng-template #undeleteFailedStrTmpl i18n>Undelete of {{idlClassDef.label}} failed or was not allowed</ng-template>
+<eg-string #undeleteFailedString [template]="undeleteFailedStrTmpl"></eg-string>
+
+<ng-template #undeleteSuccessStrTmpl i18n>{{idlClassDef.label}} Successfully undeleted</ng-template>
+<eg-string #undeleteSuccessString [template]="undeleteSuccessStrTmpl"></eg-string>
+
<ng-template #createStrTmpl i18n>{{idlClassDef.label}} Successfully Created</ng-template>
<eg-string #createString [template]="createStrTmpl"></eg-string>
</eg-grid-toolbar-button>
<eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
</eg-grid-toolbar-action>
- <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
+ <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)"
+ [disableOnRows]="shouldDisableDelete">
+ </eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Undelete Selected" i18n-label (onClick)="undeleteSelected($event)"
+ [disableOnRows]="shouldDisableUndelete" *ngIf="enableUndelete">
</eg-grid-toolbar-action>
<ng-container *ngFor="let cf of configFields">
<eg-grid-column name="{{cf.name}}" [cellTemplate]="configFieldLink">
--- /dev/null
+import { ActivatedRoute } from "@angular/router";
+import { AdminPageComponent } from "./admin-page.component";
+import { Location } from '@angular/common';
+import { FormatService } from "@eg/core/format.service";
+import { IdlService } from "@eg/core/idl.service";
+import { OrgService } from "@eg/core/org.service";
+import { AuthService } from "@eg/core/auth.service";
+import { PcrudService } from "@eg/core/pcrud.service";
+import { PermService } from "@eg/core/perm.service";
+import { ToastService } from "@eg/share/toast/toast.service";
+
+describe('CopyAttrsComponent', () => {
+ let component: AdminPageComponent;
+
+ const routeMock = jasmine.createSpyObj<ActivatedRoute>(['snapshot']);
+ const locationMock = jasmine.createSpyObj<Location>(['prepareExternalUrl']);
+ const formatMock = jasmine.createSpyObj<FormatService>(['transform']);
+ const idlMock = jasmine.createSpyObj<IdlService>(['classes']);
+ const orgMock = jasmine.createSpyObj<OrgService>(['get']);
+ const authMock = jasmine.createSpyObj<AuthService>(['user']);
+ const pcrudMock = jasmine.createSpyObj<PcrudService>(['retrieveAll']);
+ const permMock = jasmine.createSpyObj<PermService>(['hasWorkPermAt']);
+ const toastMock = jasmine.createSpyObj<ToastService>(['success']);
+ beforeEach(() => {
+ component = new AdminPageComponent(routeMock, locationMock, formatMock,
+ idlMock, orgMock, authMock, pcrudMock, permMock, toastMock);
+ })
+
+ describe('#shouldDisableDelete', () => {
+ it('returns true if one of the rows is already deleted', () => {
+ const rows = [
+ {isdeleted: () => true, a: [], classname: '', _isfieldmapper: true },
+ {isdeleted: () => false, a: [], classname: '', _isfieldmapper: true }
+ ];
+ expect(component.shouldDisableDelete(rows)).toBe(true);
+ });
+ it('returns true if no rows selected', () => {
+ expect(component.shouldDisableDelete([])).toBe(true);
+ })
+ it('returns false (i.e. you _should_ display delete) if no selected rows are deleted', () => {
+ const rows = [
+ {isdeleted: () => false, deleted: () => 'f', a: [], classname: '', _isfieldmapper: true }
+ ];
+ expect(component.shouldDisableDelete(rows)).toBe(false);
+ })
+ });
+ describe('#shouldDisableUndelete', () => {
+ it('returns true if none of the rows are deleted', () => {
+ const rows = [
+ {isdeleted: () => false, a: [], classname: '', _isfieldmapper: true },
+ {deleted: () => 'f', a: [], classname: '', _isfieldmapper: true }
+ ];
+ expect(component.shouldDisableUndelete(rows)).toBe(true);
+ });
+ it('returns true if no rows selected', () => {
+ expect(component.shouldDisableUndelete([])).toBe(true);
+ })
+ it('returns false (i.e. you _should_ display undelete) if all selected rows are deleted', () => {
+ const rows = [
+ {deleted: () => 't', a: [], classname: '', _isfieldmapper: true }
+ ];
+ expect(component.shouldDisableUndelete(rows)).toBe(false);
+ })
+ });
+});
// Disable the auto-matic org unit field filter
@Input() disableOrgFilter: boolean;
+ // Give the grid an option to undelete any deleted rows
+ @Input() enableUndelete: boolean;
+
// Include objects linking to org units which are ancestors
// of the selected org unit.
@Input() includeOrgAncestors: boolean;
@ViewChild('updateFailedString', { static: true }) updateFailedString: StringComponent;
@ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent;
@ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent;
+ @ViewChild('undeleteFailedString', { static: true }) undeleteFailedString: StringComponent;
+ @ViewChild('undeleteSuccessString', { static: true }) undeleteSuccessString: StringComponent;
@ViewChild('translator', { static: true }) translator: TranslateComponent;
idlClassDef: any;
editOneThing(idlThings.shift());
}
+ undeleteSelected(idlThings: IdlObject[]) {
+ idlThings.forEach(idlThing => idlThing.deleted(false));
+ this.pcrud.update(idlThings).subscribe(
+ val => {
+ this.undeleteSuccessString.current()
+ .then(str => this.toast.success(str));
+ },
+ err => {
+ this.undeleteFailedString.current()
+ .then(str => this.toast.danger(str));
+ },
+ () => this.grid.reload()
+ );
+ }
+
deleteSelected(idlThings: IdlObject[]) {
idlThings.forEach(idlThing => idlThing.isdeleted(true));
this.pcrud.autoApply(idlThings).subscribe(
);
}
+ shouldDisableDelete(rows: IdlObject[]): boolean {
+ if (rows.length === 0) {
+ return true;
+ } else {
+ const deletedRows = rows.filter((row) => {
+ if (row.deleted && row.deleted() === 't') {
+ return true;
+ } else if (row.isdeleted) {
+ return row.isdeleted();
+ }
+ });
+ return deletedRows.length > 0;
+ }
+ }
+
+ shouldDisableUndelete(rows: IdlObject[]): boolean {
+ if (rows.length === 0) {
+ return true;
+ } else {
+ const deletedRows = rows.filter((row) => {
+ if (row.deleted && row.deleted() === 't') {
+ return true;
+ } else if (row.isdeleted) {
+ return row.isdeleted();
+ }
+ });
+ return deletedRows.length !== rows.length;
+ }
+ }
+
createNew() {
this.editDialog.mode = 'create';
// We reuse the same editor for all actions. Be sure