From ce8bf1ffeea29194efd2cb607364061539d22590 Mon Sep 17 00:00:00 2001 From: Jacob Wood Date: Thu, 12 Jul 2018 11:15:09 -0400 Subject: [PATCH] Added truncate and truncateAfter options to numeric pipe To be consumed by future currency input control in development Resolves: #1643 --- src/demos/numeric/numeric-demo.component.html | 16 +++++ src/modules/numeric/numeric.options.ts | 2 + src/modules/numeric/numeric.pipe.spec.ts | 55 +++++++++++++++++ src/modules/numeric/numeric.pipe.ts | 21 ++++++- src/modules/numeric/numeric.service.ts | 26 +++++--- src/modules/numeric/numeric.spec.ts | 59 +++++++++++++++++++ 6 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 src/modules/numeric/numeric.pipe.spec.ts diff --git a/src/demos/numeric/numeric-demo.component.html b/src/demos/numeric/numeric-demo.component.html index 30e8caa58..95bf58fe3 100644 --- a/src/demos/numeric/numeric-demo.component.html +++ b/src/demos/numeric/numeric-demo.component.html @@ -115,6 +115,22 @@ {{1234567 | skyNumeric}}

+

+ + 1234567 without truncation + + + {{1234567 | skyNumeric:{truncate: false} }} + +

+

+ + 15501 with truncation after 10000 and 1 digit + + + {{15501 | skyNumeric:{digits: 1, truncateAfter: 10000} }} + +

15.50 as US dollar currency with 2 digits: diff --git a/src/modules/numeric/numeric.options.ts b/src/modules/numeric/numeric.options.ts index e018e8da6..cc467c02d 100644 --- a/src/modules/numeric/numeric.options.ts +++ b/src/modules/numeric/numeric.options.ts @@ -2,4 +2,6 @@ export class NumericOptions { public digits = 1; public format = 'number'; public iso = 'USD'; + public truncate: boolean = true; + public truncateAfter: number = 0; } diff --git a/src/modules/numeric/numeric.pipe.spec.ts b/src/modules/numeric/numeric.pipe.spec.ts new file mode 100644 index 000000000..201fd4fb8 --- /dev/null +++ b/src/modules/numeric/numeric.pipe.spec.ts @@ -0,0 +1,55 @@ +import { TestBed } from '@angular/core/testing'; +import { SkyNumericModule } from './numeric.module'; +import { SkyNumericService } from './numeric.service'; +import { CurrencyPipe, DecimalPipe } from '@angular/common'; +import { SkyNumericPipe } from './numeric.pipe'; + +describe('Numeric pipe', () => { + let skyNumericPipe: SkyNumericPipe; + let skyNumeric = new SkyNumericService( + new CurrencyPipe('en-US'), + new DecimalPipe('en-US') + ); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + SkyNumericModule + ], + providers: [ + SkyNumericPipe, + { + provide: SkyNumericService, + useValue: skyNumeric + } + ] + }); + }); + + beforeEach(() => { + skyNumericPipe = TestBed.get(SkyNumericPipe); + }); + + it('should pass all options to the sky numeric service', () => { + + let options: any = { + digits: 2, + format: 'currency', + iso: 'USD', + truncate: true, + truncateAfter: 10000 + }; + expect(skyNumericPipe.transform(10001.00, options)).toBe('$10K'); + }); + + it(`doesn''t require options to be set`, () => { + expect(skyNumericPipe.transform(42.87, undefined)).toBe('42.9'); + }); + + it(`doesn''t require option properties to be set`, () => { + let options: any = { + random: false + }; + expect(skyNumericPipe.transform(42.87, options)).toBe('42.9'); + }); +}); diff --git a/src/modules/numeric/numeric.pipe.ts b/src/modules/numeric/numeric.pipe.ts index 610466152..828c58c2b 100644 --- a/src/modules/numeric/numeric.pipe.ts +++ b/src/modules/numeric/numeric.pipe.ts @@ -4,8 +4,8 @@ import { NumericOptions } from './numeric.options'; /* * Shortens numbers to 1K, 1M, 1B, 1T and can format for currency - * All three arguments in the options object, Digits, format and ISO, are optional, - * defaulting to 1, number and USD respectively + * All five arguments in the options object, Digits, format, iso, truncate, truncateAfter are optional, + * defaulting to 1, number and USD, true, 0 respectively * Usage: * number_expression | skyNumeric[:numbericOptions]]] * @@ -13,12 +13,23 @@ import { NumericOptions } from './numeric.options'; * digits * format * iso + * truncate + * truncateAfter * Example: * {{ 1075 | skyNumeric:{digits: 1, format: 'currency', iso: 'USD'} }} * formats to: $1.1K * Example: * {{ 2075000 | skyNumeric:{digits: 2} }} * formats to: 2.08M + * Example: + * {{ 2075000 | skyNumeric:{truncate: false} }} + * formats to: 2,075,000 + * Example: + * {{ 9500 | skyNumeric:{truncateAfter: 10000} }} + * formats to: 9,500 + * Example: + * {{ 10001 | skyNumeric:{truncateAfter: 10000} }} + * formats to: 10K * Note: Be sure you have a space between the curly bracket surrounding the options object * and the two curly brackets closing the pipe or it will not work. Thanks angular pipes. */ @@ -43,6 +54,12 @@ export class SkyNumericPipe implements PipeTransform { if (optionsObject.iso !== undefined) { options.iso = optionsObject.iso; } + if (optionsObject.truncate !== undefined) { + options.truncate = optionsObject.truncate; + } + if (optionsObject.truncateAfter !== undefined) { + options.truncateAfter = optionsObject.truncateAfter; + } } return this.skyNumeric.formatNumber(value, options); } diff --git a/src/modules/numeric/numeric.service.ts b/src/modules/numeric/numeric.service.ts index 3a44e0e01..b23d1e1d1 100644 --- a/src/modules/numeric/numeric.service.ts +++ b/src/modules/numeric/numeric.service.ts @@ -46,11 +46,15 @@ export class SkyNumericService { value: number, options: NumericOptions ): string { + if (isNaN(value)) { + return ''; + } const decimalPlaceRegExp = /\.0+$|(\.[0-9]*[1-9])0+$/; const symbol: SkyNumericSymbol = this.symbolIndex.find((si) => { // Checks both positive and negative of value to ensure // negative numbers are shortened. - return (value >= si.value || -value >= si.value); + return options.truncate && + ((value >= options.truncateAfter && value >= si.value) || (-value >= options.truncateAfter && -value >= si.value)); }); let output: string; @@ -70,6 +74,7 @@ export class SkyNumericService { this.storeShortenSymbol(output); + let digits: string; // Checks the string entered for format. Using toLowerCase to ignore case. switch (options.format.toLowerCase()) { @@ -78,11 +83,10 @@ export class SkyNumericService { // For example, this prevents a value like $15.50 from displaying as $15.5. // Note: This will need to be reviewed if we support currencies with three decimal digits. case 'currency': - const isShortened = (value > this.symbolIndex[this.symbolIndex.length - 1].value); + const isShortened = options.truncate && (value > this.symbolIndex[this.symbolIndex.length - 1].value); const isDecimal = (value % 1 !== 0); - let digits: string; - if (!isShortened && isDecimal && options.digits >= 2) { + if ((!isShortened && isDecimal && options.digits >= 2) || !options.truncate) { digits = `1.2-${options.digits}`; } else { digits = `1.0-${options.digits}`; @@ -101,16 +105,22 @@ export class SkyNumericService { // it will be treated like a number. default: // Ensures localization of the number to ensure comma and - // decimal separators are correct. + // decimal separator + if (!options.truncate) { + digits = `1.${options.digits}-${options.digits}`; + } else { + digits = `1.0-${options.digits}`; + } output = this.decimalPipe.transform( parseFloat(output), - `1.0-${options.digits}` + digits ); break; } - output = this.replaceShortenSymbol(output); - + if (options.truncate) { + output = this.replaceShortenSymbol(output); + } return output; } diff --git a/src/modules/numeric/numeric.spec.ts b/src/modules/numeric/numeric.spec.ts index a088ce62f..ee183acae 100644 --- a/src/modules/numeric/numeric.spec.ts +++ b/src/modules/numeric/numeric.spec.ts @@ -25,6 +25,14 @@ describe('Numeric service', () => { expect(skyNumeric.formatNumber(value, options)).toBe('0'); }); + it('formats undefined as blank', () => { + let value = undefined; + let options = new NumericOptions(); + options.digits = 0; + + expect(skyNumeric.formatNumber(value, options)).toBe(''); + }); + it('formats 100 with 0 digits as 100', () => { const value = 100; let options = new NumericOptions(); @@ -40,6 +48,14 @@ describe('Numeric service', () => { expect(skyNumeric.formatNumber(value, options)).toBe('1K'); }); + it('does not truncate 1000 with 2 digits as 1K when truncate is false', () => { + const value = 1000; + let options = new NumericOptions(); + options.digits = 2; + options.truncate = false; + expect(skyNumeric.formatNumber(value, options)).toBe('1,000.00'); + }); + it('formats 1000000 with 0 digits as 1M', () => { const value = 1000000; let options = new NumericOptions(); @@ -47,6 +63,14 @@ describe('Numeric service', () => { expect(skyNumeric.formatNumber(value, options)).toBe('1M'); }); + it('does not truncate 1000000 with 2 digits as 1M when truncate is false', () => { + const value = 1000000; + let options = new NumericOptions(); + options.digits = 2; + options.truncate = false; + expect(skyNumeric.formatNumber(value, options)).toBe('1,000,000.00'); + }); + it('formats 1000000000 with 0 digits as 1B', () => { const value = 1000000000; let options = new NumericOptions(); @@ -54,6 +78,14 @@ describe('Numeric service', () => { expect(skyNumeric.formatNumber(value, options)).toBe('1B'); }); + it('does not truncate 1000000000 with 2 digits as 1B when truncate is false', () => { + const value = 1000000000; + let options = new NumericOptions(); + options.digits = 2; + options.truncate = false; + expect(skyNumeric.formatNumber(value, options)).toBe('1,000,000,000.00'); + }); + it('formats 1000000000000 with 0 digits as 1T', () => { const value = 1000000000000; let options = new NumericOptions(); @@ -61,6 +93,14 @@ describe('Numeric service', () => { expect(skyNumeric.formatNumber(value, options)).toBe('1T'); }); + it('does not truncate 1000000000000 with 2 digits as 1T when truncate is false', () => { + const value = 1000000000000; + let options = new NumericOptions(); + options.digits = 2; + options.truncate = false; + expect(skyNumeric.formatNumber(value, options)).toBe('1,000,000,000,000.00'); + }); + it('formats 999000000 as 999M', () => { const value = 999000000; let options = new NumericOptions(); @@ -124,6 +164,25 @@ describe('Numeric service', () => { options.format = 'currency'; expect(skyNumeric.formatNumber(value, options)).toBe('£15.50'); }); + + // Testing ability only after a certain value is specified + // using the truncateAfter configuration property + it('does not truncate 5000 to 5K when truncateAfter set to 10000', () => { + const value = 5000; + let options = new NumericOptions(); + options.digits = 0; + options.truncateAfter = 10000; + expect(skyNumeric.formatNumber(value, options)).toBe('5,000'); + }); + + it('formats 10001 to 10K when truncateAfter set to 10000', () => { + const value = 10001; + let options = new NumericOptions(); + options.digits = 0; + options.truncateAfter = 10000; + expect(skyNumeric.formatNumber(value, options)).toBe('10K'); + }); + // Adjusting test to expect either format of a negative. MS browsers use system's Region // setting for Currency formatting. For Negative currency, the windows default is parentheses // around the number. All other browsers use a preceeding negative sign (-).