diff --git a/CHANGELOG.md b/CHANGELOG.md index 493cb18c..79a96183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Upcoming Version (WIP) ================== Improvements: +* [OLMIS-8118](https://openlmis.atlassian.net/browse/OLMIS-8118): Validate max value on positive integer inputs. * [SELV3-748](https://openlmis.atlassian.net/browse/SELV3-748) Resolved cookie issue causing untranslated warning messages * [OLMIS-8192](https://openlmis.atlassian.net/browse/OLMIS-8192): Editable text inputs inside table cells now grow with their content. diff --git a/src/openlmis-form/positive-integer.directive.js b/src/openlmis-form/positive-integer.directive.js index cd206afa..556158c9 100644 --- a/src/openlmis-form/positive-integer.directive.js +++ b/src/openlmis-form/positive-integer.directive.js @@ -23,16 +23,26 @@ * @name openlmis-form.directive:positiveInteger * * @description - * Restricts the ngModel to only allow positive integers. + * Restricts the ngModel to only allow positive integers. Values larger than the maximum safe + * integer in JavaScript are rejected (the 'max' validity is set to false), because parseInt + * would otherwise silently overflow and send a wrong value. When the java-integer attribute is + * present the limit is lowered to the maximum Java Integer value. * * @example * Extend the input element to force it to only accept positive integers as values. * ``` * * ``` + * Add the java-integer attribute to additionally reject values exceeding the Java Integer range. + * ``` + * + * ``` */ + var JAVA_INTEGER_MAX = Math.pow(2, 31) - 1; + angular .module('openlmis-form') + .constant('JAVA_INTEGER_MAX', JAVA_INTEGER_MAX) .directive('positiveInteger', positiveInteger); function positiveInteger() { @@ -59,7 +69,14 @@ modelCtrl.$render(); } - return transformedInput ? parseInt(transformedInput) : null; + var parsed = transformedInput ? parseInt(transformedInput) : null; + + var maxValue = attrs.hasOwnProperty('javaInteger') + ? JAVA_INTEGER_MAX + : Number.MAX_SAFE_INTEGER; + modelCtrl.$setValidity('max', !parsed || parsed <= maxValue); + + return parsed; }); } } diff --git a/src/openlmis-form/positive-integer.directive.spec.js b/src/openlmis-form/positive-integer.directive.spec.js new file mode 100644 index 00000000..b62280cd --- /dev/null +++ b/src/openlmis-form/positive-integer.directive.spec.js @@ -0,0 +1,118 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ +describe('openlmisPositiveInteger', function() { + + beforeEach(function() { + module('openlmis-form'); + + inject(function($injector) { + this.$compile = $injector.get('$compile'); + this.$rootScope = $injector.get('$rootScope'); + }); + + this.$scope = this.$rootScope.$new(); + + this.compileElement = function(extraAttrs) { + this.element = this.$compile( + '
' + + '
' + )(this.$scope); + angular.element('body').append(this.element); + this.$rootScope.$apply(); + }; + + this.compileElement(); + }); + + it('should add number class', function() { + expect(this.$scope.form_test.input_test.$$element.hasClass('number')).toBe(true); + }); + + it('should strip non-numeric characters', function() { + this.$scope.form_test.input_test.$setViewValue('12abc3'); + this.$scope.$digest(); + + expect(this.$scope.example).toEqual(123); + }); + + it('should parse value as integer', function() { + this.$scope.form_test.input_test.$setViewValue('42'); + this.$scope.$digest(); + + expect(this.$scope.example).toEqual(42); + }); + + it('should return null for empty input', function() { + this.$scope.form_test.input_test.$setViewValue(''); + this.$scope.$digest(); + + expect(this.$scope.example).toBeNull(); + }); + + describe('without java-integer attribute', function() { + + it('should be valid for value within the safe integer range', function() { + this.$scope.form_test.input_test.$setViewValue('9999999999'); + this.$scope.$digest(); + + expect(this.$scope.form_test.input_test.$error.max).toBeFalsy(); + }); + + it('should be invalid for value exceeding the max safe integer', function() { + this.$scope.form_test.input_test.$setViewValue('99999999999999999'); + this.$scope.$digest(); + + expect(this.$scope.form_test.input_test.$error.max).toBe(true); + }); + }); + + describe('with java-integer attribute', function() { + + beforeEach(function() { + this.compileElement('java-integer'); + }); + + it('should be valid for value within Java Integer range', function() { + this.$scope.form_test.input_test.$setViewValue('2147483647'); + this.$scope.$digest(); + + expect(this.$scope.form_test.input_test.$error.max).toBeFalsy(); + expect(this.$scope.form_test.input_test.$valid).toBe(true); + }); + + it('should be invalid for value exceeding Java Integer max', function() { + this.$scope.form_test.input_test.$setViewValue('2147483648'); + this.$scope.$digest(); + + expect(this.$scope.form_test.input_test.$error.max).toBe(true); + expect(this.$scope.example).toBe(2147483648); + }); + + it('should be valid for empty value', function() { + this.$scope.form_test.input_test.$setViewValue(''); + this.$scope.$digest(); + + expect(this.$scope.form_test.input_test.$error.max).toBeFalsy(); + }); + + it('should be valid for typical small quantity', function() { + this.$scope.form_test.input_test.$setViewValue('100'); + this.$scope.$digest(); + + expect(this.$scope.form_test.input_test.$error.max).toBeFalsy(); + }); + }); +}); \ No newline at end of file