From 873e26347fd5bb3d978d16d272db30cc5e49241a Mon Sep 17 00:00:00 2001 From: kirk Date: Fri, 17 Nov 2017 05:47:18 -0500 Subject: [PATCH 1/3] docs(linky): mark "target" param as optional This argument is optional in practice, and it is not provided in many of the examples in the documentation. Its optional presence is handled here: https://github.com/angular/angular.js/blob/f876ab71913e17e9126baad19ab795f28b61bfe6/src/ngSanitize/filter/linky.js#L185 Closes #16330 --- src/ngSanitize/filter/linky.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ngSanitize/filter/linky.js b/src/ngSanitize/filter/linky.js index 34881c847729..564799d59e4b 100644 --- a/src/ngSanitize/filter/linky.js +++ b/src/ngSanitize/filter/linky.js @@ -12,7 +12,7 @@ * Requires the {@link ngSanitize `ngSanitize`} module to be installed. * * @param {string} text Input text. - * @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in. + * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in. * @param {object|function(url)} [attributes] Add custom attributes to the link element. * * Can be one of: From 12cf994fccd7df6c2c2ba07d50b921ee80d62be7 Mon Sep 17 00:00:00 2001 From: Denys B Date: Fri, 17 Nov 2017 12:55:18 +0200 Subject: [PATCH 2/3] fix($compile): sanitize special chars in directive name This fixes regression bug when directive name with preceeding special char in HTML markup does not match the registered name. (introduced in https://github.com/angular/angular.js/commit/73050cdda04675bfa6705dc841ddbbb6919eb048) Closes #16314 Closes #16278 --- src/ng/compile.js | 4 +++- test/ng/compileSpec.js | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 561bb13fef16..4ec3ea5d6d94 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -3647,7 +3647,9 @@ var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; function directiveNormalize(name) { return name .replace(PREFIX_REGEXP, '') - .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace); + .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }); } /** diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 2890a76ac0c0..ca2d43380e8f 100644 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -294,6 +294,27 @@ describe('$compile', function() { inject(function($compile) {}); }); + it('should ignore special chars before processing attribute directive name', function() { + // a regression https://github.com/angular/angular.js/issues/16278 + module(function() { + directive('t', function(log) { + return { + restrict: 'A', + link: { + pre: log.fn('pre'), + post: log.fn('post') + } + }; + }); + }); + inject(function($compile, $rootScope, log) { + $compile('
')($rootScope); + $compile('
')($rootScope); + $compile('
')($rootScope); + expect(log).toEqual('pre; post; pre; post; pre; post'); + }); + }); + it('should throw an exception if the directive factory is not defined', function() { module(function() { expect(function() { From aa3f951330ec7b10b43ea884d9b5754e296770ec Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Fri, 17 Nov 2017 12:28:03 +0100 Subject: [PATCH 3/3] fix(input[number]): validate min/max against viewValue This brings the validation in line with HTML5 validation, i.e. what the user has entered is validated, and not a possibly transformed value. Fixes #12761 Closes #16325 BREAKING CHANGE `input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`. This affects apps that use `$parsers` or `$formatters` to transform the input / model value. If you rely on the $modelValue validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object: ``` { restrict: 'A', require: 'ngModel', link: function(scope, element, attrs, ctrl) { var maxValidator = ctrl.$validators.max; ctrk.$validators.max = function(modelValue, viewValue) { return maxValidator(modelValue, modelValue); }; } } ``` --- src/ng/directive/input.js | 8 +-- test/ng/directive/inputSpec.js | 98 ++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index f87e755b8c8d..ebae9b3bed18 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1604,8 +1604,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { var maxVal; if (isDefined(attr.min) || attr.ngMin) { - ctrl.$validators.min = function(value) { - return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; + ctrl.$validators.min = function(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; }; attr.$observe('min', function(val) { @@ -1616,8 +1616,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { } if (isDefined(attr.max) || attr.ngMax) { - ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; + ctrl.$validators.max = function(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; }; attr.$observe('max', function(val) { diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 260e11d1d4b7..d1a552194c4e 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -2284,6 +2284,15 @@ describe('input', function() { describe('number', function() { + // Helpers for min / max tests + var subtract = function(value) { + return value - 5; + }; + + var add = function(value) { + return value + 5; + }; + it('should reset the model if view is invalid', function() { var inputElm = helper.compileInput(''); @@ -2465,6 +2474,29 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should validate against the viewValue', function() { + var inputElm = helper.compileInput( + ''); + + var ngModelCtrl = inputElm.controller('ngModel'); + ngModelCtrl.$parsers.push(subtract); + + helper.changeInputValueTo('10'); + expect(inputElm).toBeValid(); + expect($rootScope.value).toBe(5); + expect($rootScope.form.alias.$error.min).toBeFalsy(); + + ngModelCtrl.$parsers.pop(); + ngModelCtrl.$parsers.push(add); + + helper.changeInputValueTo('5'); + expect(inputElm).toBeInvalid(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBe(10); + }); + + it('should validate even if min value changes on-the-fly', function() { $rootScope.min = undefined; var inputElm = helper.compileInput(''); @@ -2511,6 +2543,28 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should validate against the viewValue', function() { + var inputElm = helper.compileInput( + ''); + var ngModelCtrl = inputElm.controller('ngModel'); + ngModelCtrl.$parsers.push(subtract); + + helper.changeInputValueTo('10'); + expect(inputElm).toBeValid(); + expect($rootScope.value).toBe(5); + expect($rootScope.form.alias.$error.min).toBeFalsy(); + + ngModelCtrl.$parsers.pop(); + ngModelCtrl.$parsers.push(add); + + helper.changeInputValueTo('5'); + expect(inputElm).toBeInvalid(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBe(10); + }); + + it('should validate even if the ngMin value changes on-the-fly', function() { $rootScope.min = undefined; var inputElm = helper.compileInput(''); @@ -2558,6 +2612,28 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); }); + + it('should validate against the viewValue', function() { + var inputElm = helper.compileInput(''); + var ngModelCtrl = inputElm.controller('ngModel'); + ngModelCtrl.$parsers.push(add); + + helper.changeInputValueTo('10'); + expect(inputElm).toBeValid(); + expect($rootScope.value).toBe(15); + expect($rootScope.form.alias.$error.max).toBeFalsy(); + + ngModelCtrl.$parsers.pop(); + ngModelCtrl.$parsers.push(subtract); + + helper.changeInputValueTo('15'); + expect(inputElm).toBeInvalid(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBe(10); + }); + + it('should validate even if max value changes on-the-fly', function() { $rootScope.max = undefined; var inputElm = helper.compileInput(''); @@ -2604,6 +2680,28 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); }); + + it('should validate against the viewValue', function() { + var inputElm = helper.compileInput(''); + var ngModelCtrl = inputElm.controller('ngModel'); + ngModelCtrl.$parsers.push(add); + + helper.changeInputValueTo('10'); + expect(inputElm).toBeValid(); + expect($rootScope.value).toBe(15); + expect($rootScope.form.alias.$error.max).toBeFalsy(); + + ngModelCtrl.$parsers.pop(); + ngModelCtrl.$parsers.push(subtract); + + helper.changeInputValueTo('15'); + expect(inputElm).toBeInvalid(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBe(10); + }); + + it('should validate even if the ngMax value changes on-the-fly', function() { $rootScope.max = undefined; var inputElm = helper.compileInput('');