LP#1775466 move format svc to core w/ unit tests; root inject core svcs
authorBill Erickson <berickxx@gmail.com>
Thu, 5 Jul 2018 15:06:20 +0000 (11:06 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 5 Jul 2018 15:07:22 +0000 (11:07 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
19 files changed:
Open-ILS/src/eg2/src/app/common.module.ts
Open-ILS/src/eg2/src/app/core/auth.service.ts
Open-ILS/src/eg2/src/app/core/event.service.ts
Open-ILS/src/eg2/src/app/core/format.service.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/core/format.spec.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/core/idl.service.ts
Open-ILS/src/eg2/src/app/core/net.service.ts
Open-ILS/src/eg2/src/app/core/org.service.ts
Open-ILS/src/eg2/src/app/core/pcrud.service.ts
Open-ILS/src/eg2/src/app/core/perm.service.ts
Open-ILS/src/eg2/src/app/core/store.service.ts
Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.ts
Open-ILS/src/eg2/src/app/share/util/format.service.ts [deleted file]
Open-ILS/src/eg2/src/app/staff/common.module.ts
Open-ILS/src/eg2/src/app/staff/login.component.ts
Open-ILS/src/eg2/src/app/staff/resolver.service.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/test_data/eg_mock.js

index 48e19d9..c83ad39 100644 (file)
@@ -7,16 +7,13 @@ import {RouterModule} from '@angular/router';
 import {FormsModule} from '@angular/forms';
 import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
 
-import {EventService} from '@eg/core/event.service';
-import {StoreService} from '@eg/core/store.service';
-import {IdlService} from '@eg/core/idl.service';
-import {NetService} from '@eg/core/net.service';
-import {AuthService} from '@eg/core/auth.service';
-import {PermService} from '@eg/core/perm.service';
-import {PcrudService} from '@eg/core/pcrud.service';
-import {OrgService} from '@eg/core/org.service';
-import {AudioService} from '@eg/share/util/audio.service';
-import {FormatService} from '@eg/share/util/format.service';
+/*
+Note core services are injected into 'root'.
+They do not have to be added to the providers list.
+*/
+
+// consider moving these to core...
+import {FormatService} from '@eg/core/format.service';
 import {PrintService} from '@eg/share/print/print.service';
 
 // Globally available components
@@ -65,16 +62,7 @@ export class EgCommonModule {
             providers: [
                 DatePipe,
                 CurrencyPipe,
-                EventService,
-                StoreService,
-                IdlService,
-                NetService,
-                AuthService,
-                PermService,
-                PcrudService,
-                OrgService,
                 PrintService,
-                AudioService,
                 FormatService
             ]
         };
index 1de67db..7d58dda 100644 (file)
@@ -37,7 +37,7 @@ export enum AuthWsState {
     VALID
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class AuthService {
 
     private authChannel: any;
index 638ba96..0bbf60b 100644 (file)
@@ -24,7 +24,7 @@ export class EgEvent {
     }
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class EventService {
 
     /**
diff --git a/Open-ILS/src/eg2/src/app/core/format.service.ts b/Open-ILS/src/eg2/src/app/core/format.service.ts
new file mode 100644 (file)
index 0000000..f8bf0d9
--- /dev/null
@@ -0,0 +1,102 @@
+import {Injectable} from '@angular/core';
+import {DatePipe, CurrencyPipe} from '@angular/common';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+
+/**
+ * Format IDL vield values for display.
+ */
+
+declare var OpenSRF;
+
+export interface FormatParams {
+    value: any;
+    idlClass?: string;
+    idlField?: string;
+    datatype?: string;
+    orgField?: string; // 'shortname' || 'name'
+    datePlusTime?: boolean;
+}
+
+@Injectable({providedIn: 'root'})
+export class FormatService {
+
+    dateFormat = 'shortDate';
+    dateTimeFormat = 'short';
+    wsOrgTimezone: string = OpenSRF.tz;
+
+    constructor(
+        private datePipe: DatePipe,
+        private currencyPipe: CurrencyPipe,
+        private idl: IdlService,
+        private org: OrgService
+    ) {
+
+        // Create an inilne polyfill for Number.isNaN, which is
+        // not available in PhantomJS for unit testing.
+        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
+        if (!Number.isNaN) {
+            // "The following works because NaN is the only value 
+            // in javascript which is not equal to itself."
+            Number.isNaN = (value: any) => {
+                return value !== value;
+            };
+        }
+    }
+
+    /**
+     * Create a human-friendly display version of any field type.
+     */
+    transform(params: FormatParams): string {
+        const value = params.value;
+
+        if (   value === undefined
+            || value === null
+            || value === ''
+            || Number.isNaN(value)) {
+            return '';
+        }
+
+        let datatype = params.datatype;
+
+        if (!datatype) {
+            if (params.idlClass && params.idlField) {
+                datatype = this.idl.classes[params.idlClass]
+                    .field_map[params.idlField].datatype;
+            } else {
+                // Assume it's a primitive value
+                return value + '';
+            }
+        }
+
+        switch (datatype) {
+
+            case 'org_unit':
+                const orgField = params.orgField || 'shortname';
+                const org = this.org.get(value);
+                return org ? org[orgField]() : '';
+
+            case 'timestamp':
+                const date = new Date(value);
+                let fmt = this.dateFormat || 'shortDate';
+                if (params.datePlusTime) {
+                    fmt = this.dateTimeFormat || 'short';
+                }
+                return this.datePipe.transform(date, fmt);
+
+            case 'money':
+                return this.currencyPipe.transform(value);
+
+            case 'bool':
+                // Slightly better than a bare 't' or 'f'.
+                // Should probably add a global true/false string.
+                return Boolean(
+                    value === 't' || value === 1 || value === '1'
+                ).toString();
+
+            default:
+                return value + '';
+        }
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/core/format.spec.ts b/Open-ILS/src/eg2/src/app/core/format.spec.ts
new file mode 100644 (file)
index 0000000..05991df
--- /dev/null
@@ -0,0 +1,90 @@
+import {DatePipe, CurrencyPipe} from '@angular/common';
+import {IdlService} from './idl.service';
+import {EventService} from './event.service';
+import {NetService} from './net.service';
+import {AuthService} from './auth.service';
+import {PcrudService} from './pcrud.service';
+import {StoreService} from './store.service';
+import {OrgService} from './org.service';
+import {FormatService} from './format.service';
+
+
+describe('FormatService', () => {
+
+    let currencyPipe: CurrencyPipe;
+    let datePipe: DatePipe;
+    let idlService: IdlService;
+    let netService: NetService;
+    let authService: AuthService;
+    let pcrudService: PcrudService;
+    let orgService: OrgService;
+    let evtService: EventService;
+    let storeService: StoreService;
+    let service: FormatService;
+
+    beforeEach(() => {
+        currencyPipe = new CurrencyPipe('en');
+        datePipe = new DatePipe('en');
+        idlService = new IdlService();
+        evtService = new EventService();
+        storeService = new StoreService(null /* CookieService */);
+        netService = new NetService(evtService);
+        authService = new AuthService(evtService, netService, storeService);
+        pcrudService = new PcrudService(idlService, netService, authService);
+        orgService = new OrgService(netService, authService, pcrudService);
+        service = new FormatService(
+            datePipe,
+            currencyPipe,
+            idlService,
+            orgService
+        );
+    });
+
+    const initTestData = () => {
+        idlService.parseIdl();
+        const win: any = window; // trick TS
+        win._eg_mock_data.generateOrgTree(idlService, orgService);
+    };
+
+    it('should format an org unit name', () => {
+        initTestData();
+        const str = service.transform({
+            value: orgService.root(),
+            datatype: 'org_unit',
+            orgField: 'shortname' // currently the default
+        });
+        expect(str).toBe('ROOT');  // from eg_mock.js
+    });
+
+    it('should format a date', () => {
+        initTestData();
+        const str = service.transform({
+            value: new Date(2018, 6, 5),
+            datatype: 'timestamp',
+        });
+        expect(str).toBe('7/5/18');
+    });
+
+    it('should format a date plus time', () => {
+        initTestData();
+        const str = service.transform({
+            value: new Date(2018, 6, 5, 12, 30, 1),
+            datatype: 'timestamp',
+            datePlusTime: true
+        });
+        expect(str).toBe('7/5/18, 12:30 PM');
+    });
+
+
+
+    it('should format money', () => {
+        initTestData();
+        const str = service.transform({
+            value: '12.1',
+            datatype: 'money'
+        });
+        expect(str).toBe('$12.10');
+    });
+
+});
+
index 3f9bbe8..95a92a4 100644 (file)
@@ -14,7 +14,7 @@ export interface IdlObject {
     [fields: string]: any;
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class IdlService {
 
     classes: any = {}; // IDL class metadata
index df3cb3e..3c3435b 100644 (file)
@@ -68,7 +68,7 @@ export interface AuthExpiredEvent {
     viaExternal?: boolean;
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class NetService {
 
     permFailed$: EventEmitter<NetRequest>;
index 57cbf6f..38faaff 100644 (file)
@@ -19,7 +19,7 @@ interface OrgSettingsBatch {
     [key: string]: any;
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class OrgService {
 
     private orgList: IdlObject[] = [];
index ea1dabc..76ee341 100644 (file)
@@ -251,7 +251,7 @@ export class PcrudContext {
     }
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class PcrudService {
 
     constructor(
index 774c045..44d3c63 100644 (file)
@@ -11,7 +11,7 @@ interface HasPermHereResult {
     [permName: string]: boolean;
 }
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class PermService {
 
     constructor(
index 0a20ca8..5a78f84 100644 (file)
@@ -4,7 +4,7 @@
 import {Injectable} from '@angular/core';
 import {CookieService} from 'ngx-cookie';
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class StoreService {
 
     // Base path for cookie-based storage.
index a91e3d2..dc04134 100644 (file)
@@ -4,7 +4,7 @@ import {Subscription} from 'rxjs/Subscription';
 import {IdlService} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 import {StoreService} from '@eg/core/store.service';
-import {FormatService} from '@eg/share/util/format.service';
+import {FormatService} from '@eg/core/format.service';
 import {GridContext, GridColumn, GridDataSource, GridRowFlairEntry} from './grid';
 
 /**
index 18c580c..c395687 100644 (file)
@@ -7,7 +7,7 @@ import {Subscription} from 'rxjs/Subscription';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 import {StoreService} from '@eg/core/store.service';
-import {FormatService} from '@eg/share/util/format.service';
+import {FormatService} from '@eg/core/format.service';
 import {Pager} from '@eg/share/util/pager';
 
 const MAX_ALL_ROW_COUNT = 10000;
diff --git a/Open-ILS/src/eg2/src/app/share/util/format.service.ts b/Open-ILS/src/eg2/src/app/share/util/format.service.ts
deleted file mode 100644 (file)
index 33d3253..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-import {Injectable} from '@angular/core';
-import {DatePipe, CurrencyPipe} from '@angular/common';
-import {IdlService, IdlObject} from '@eg/core/idl.service';
-import {OrgService} from '@eg/core/org.service';
-
-/**
- * Format IDL vield values for display.
- */
-
-declare var OpenSRF;
-
-export interface FormatParams {
-    value: any;
-    idlClass?: string;
-    idlField?: string;
-    datatype?: string;
-    orgField?: string; // 'shortname' || 'name'
-    datePlusTime?: boolean;
-}
-
-@Injectable()
-export class FormatService {
-
-    dateFormat = 'shortDate';
-    dateTimeFormat = 'short';
-    wsOrgTimezone: string = OpenSRF.tz;
-
-    constructor(
-        private datePipe: DatePipe,
-        private currencyPipe: CurrencyPipe,
-        private idl: IdlService,
-        private org: OrgService
-    ) {}
-
-    /**
-     * Create a human-friendly display version of any field type.
-     */
-    transform(params: FormatParams): string {
-        const value = params.value;
-
-        if (   value === undefined
-            || value === null
-            || value === ''
-            || Number.isNaN(value)) {
-            return '';
-        }
-
-        let datatype = params.datatype;
-
-        if (!datatype) {
-            if (params.idlClass && params.idlField) {
-                datatype = this.idl.classes[params.idlClass]
-                    .field_map[params.idlField].datatype;
-            } else {
-                // Assume it's a primitive value
-                return value + '';
-            }
-        }
-
-        switch (datatype) {
-
-            case 'org_unit':
-                const orgField = params.orgField || 'shortname';
-                const org = this.org.get(value);
-                return org ? org[orgField]() : '';
-
-            case 'timestamp':
-                const date = new Date(value);
-                let fmt = this.dateFormat || 'shortDate';
-                if (params.datePlusTime) {
-                    fmt = this.dateTimeFormat || 'short';
-                }
-                return this.datePipe.transform(date, fmt);
-
-            case 'money':
-                return this.currencyPipe.transform(value);
-
-            case 'bool':
-                // Slightly better than a bare 't' or 'f'.
-                // Should probably add a global true/false string.
-                return Boolean(
-                    value === 't' || value === 1 || value === '1'
-                ).toString();
-
-            default:
-                return value + '';
-        }
-    }
-}
-
index 986a19a..da3bc32 100644 (file)
@@ -1,5 +1,6 @@
 import {NgModule, ModuleWithProviders} from '@angular/core';
 import {EgCommonModule} from '@eg/common.module';
+import {AudioService} from '@eg/share/util/audio.service';
 import {StaffBannerComponent} from './share/staff-banner.component';
 import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
 import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
@@ -58,6 +59,7 @@ export class StaffCommonModule {
             ngModule: StaffCommonModule,
             providers: [ // Export staff-wide services
                 AccessKeyService,
+                AudioService,
                 StringService,
                 ToastService
             ]
index a5f2aa0..b8e1b95 100644 (file)
@@ -57,7 +57,11 @@ export class StaffLoginComponent implements OnInit {
     handleSubmit() {
 
         // post-login URL
-        const url: string = this.auth.redirectUrl || '/staff/splash';
+        let url: string = this.auth.redirectUrl || '/staff/splash';
+
+        // prevent sending the user back to the login page.
+        if (url.startsWith('/staff/login')) { url = '/staff/splash'; }
+
         const workstation: string = this.args.workstation;
 
         this.auth.login(this.args).then(
index 254d03b..2b532c3 100644 (file)
@@ -10,7 +10,7 @@ import {NetService} from '@eg/core/net.service';
 import {AuthService, AuthWsState} from '@eg/core/auth.service';
 import {PermService} from '@eg/core/perm.service';
 import {OrgService} from '@eg/core/org.service';
-import {FormatService} from '@eg/share/util/format.service';
+import {FormatService} from '@eg/core/format.service';
 
 const LOGIN_PATH = '/staff/login';
 const WS_MANAGE_PATH = '/staff/admin/workstation/workstations/manage';
index dbcd306..db47b59 100644 (file)
@@ -49,7 +49,7 @@
       [entries]="cbEntries"></eg-combobox>
   </div>
   <div class="col-lg-3">
-    <eg-combobox [allowFreeText]="true" 
+    <eg-combobox
       placeholder="Combobox with dynamic data"
       [asyncDataSource]="cbAsyncSource"></eg-combobox>
   </div>
index 54e4eae..3db3579 100644 (file)
@@ -24,6 +24,7 @@ window._eg_mock_data = {
         var org1 = idlService.create('aou'); 
         org1.id(1);
         org1.ou_type(type1);
+        org1.shortname('ROOT');
 
         var org2 = idlService.create('aou'); 
         org2.id(2);