diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 3ba4bdc3615f..0ca837ad7e45 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -830,6 +830,62 @@ var inputType = {
*/
'radio': radioInputType,
+ /**
+ * @ngdoc input
+ * @name input[range]
+ *
+ * @description
+ * Native range input with number validation and transformation. Sets the `number` validation
+ * to always have a valid number.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation to ensure that the value entered is greater the `min`.
+ * @param {string=} max Sets the `max` validation to ensure that the value entered is lesser than `max`.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+
+
+
+
+
+
+ var value = element(by.binding('value'));
+ var valid = element(by.binding('myForm.input.$valid'));
+ var input = element(by.model('value'));
+
+ it('should initialize to model', function() {
+ expect(value.getText()).toContain('50');
+ expect(valid.getText()).toContain('true');
+ });
+
+ it('should respect the max', function() {
+ input.clear();
+ input.sendKeys('123');
+ expect(value.getText()).toEqual('value = 100');
+ expect(valid.getText()).toContain('true');
+ });
+
+
+ */
+ 'range': numberInputType,
/**
* @ngdoc input
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 3727ef87fe90..114e49ee89a8 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -3906,6 +3906,662 @@ describe('input', function() {
});
});
+ describe('range', function() {
+
+ it('should reset the model if view is invalid', function() {
+ compileInput('');
+
+ scope.$apply('age = 100');
+ expect(inputElm.val()).toBe('100');
+
+ try {
+ // to allow non-number values, we have to change type so that
+ // the browser which have number validation will not interfere with
+ // this test. IE8 won't allow it hence the catch.
+ inputElm[0].setAttribute('type', 'text');
+ } catch (e) {}
+
+ changeInputValueTo('100X');
+ expect(inputElm.val()).toBe('100X');
+ expect(scope.age).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+
+ it('should render as 50 if null', function() {
+ compileInput('');
+
+ scope.$apply('age = null');
+
+ expect(scope.age).toBeNull();
+ expect(inputElm.val()).toEqual('50');
+ });
+
+ it('should come up as 50 when no value specified', function() {
+ compileInput('');
+
+ expect(inputElm.val()).toBe('50');
+
+ scope.$apply('age = null');
+
+ expect(scope.age).toBeNull();
+ expect(inputElm.val()).toBe('50');
+ });
+
+
+ it('should parse empty string to 50', function() {
+ compileInput('');
+
+ scope.$apply('age = 10');
+
+ changeInputValueTo('');
+ expect(scope.age).toBe(50);
+ expect(inputElm).toBeValid();
+ });
+
+
+ it('should only invalidate the model if suffering from bad input when the data is parsed', function() {
+ compileInput('', {
+ valid: false,
+ badInput: true
+ });
+
+ expect(scope.age).toBeUndefined();
+ expect(inputElm).toBeValid();
+
+ changeInputValueTo('this-will-fail-because-of-the-badInput-flag');
+
+ expect(scope.age).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+
+ it('should validate with undefined viewValue when $validate() called', function() {
+ compileInput('');
+
+ scope.form.alias.$validate();
+
+ expect(inputElm).toBeValid();
+ expect(scope.form.alias.$error.number).toBeUndefined();
+ });
+
+
+ it('should throw if the model value is not a number', function() {
+ expect(function() {
+ scope.value = 'one';
+ compileInput('');
+ }).toThrowMinErr('ngModel', 'numfmt', "Expected `one` to be a number");
+ });
+
+
+ describe('min', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('5');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(10);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+
+ changeInputValueTo('100');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(100);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+
+ it('should validate even if min value changes on-the-fly', function() {
+ scope.min = 10;
+ compileInput('');
+
+ changeInputValueTo('15');
+ expect(inputElm).toBeValid();
+
+ scope.min = 20;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.min = '20';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+ describe('ngMin', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('1');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+
+ changeInputValueTo('100');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(100);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+
+ it('should validate even if the ngMin value changes on-the-fly', function() {
+ scope.min = 10;
+ compileInput('');
+
+ changeInputValueTo('15');
+ expect(inputElm).toBeValid();
+
+ scope.min = 20;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.min = '20';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+
+ describe('max', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('20');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(10);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+
+ changeInputValueTo('0');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(0);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
+ it('should validate even if max value changes on-the-fly', function() {
+ scope.max = 10;
+ compileInput('');
+
+ changeInputValueTo('5');
+ expect(inputElm).toBeValid();
+
+ scope.max = 0;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.max = '4';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+ describe('ngMax', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('20');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeUndefined();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+
+ changeInputValueTo('0');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(0);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
+ it('should validate even if the ngMax value changes on-the-fly', function() {
+ scope.max = 10;
+ compileInput('');
+
+ changeInputValueTo('5');
+ expect(inputElm).toBeValid();
+
+ scope.max = 0;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.max = '4';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+ });
+
+
+ describe('number', function() {
+
+ it('should reset the model if view is invalid', function() {
+ compileInput('');
+
+ scope.$apply('age = 123');
+ expect(inputElm.val()).toBe('123');
+
+ try {
+ // to allow non-number values, we have to change type so that
+ // the browser which have number validation will not interfere with
+ // this test. IE8 won't allow it hence the catch.
+ inputElm[0].setAttribute('type', 'text');
+ } catch (e) {}
+
+ changeInputValueTo('123X');
+ expect(inputElm.val()).toBe('123X');
+ expect(scope.age).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+
+ it('should render as blank if null', function() {
+ compileInput('');
+
+ scope.$apply('age = null');
+
+ expect(scope.age).toBeNull();
+ expect(inputElm.val()).toEqual('');
+ });
+
+ it('should come up blank when no value specified', function() {
+ compileInput('');
+
+ expect(inputElm.val()).toBe('');
+
+ scope.$apply('age = null');
+
+ expect(scope.age).toBeNull();
+ expect(inputElm.val()).toBe('');
+ });
+
+
+ it('should parse empty string to null', function() {
+ compileInput('');
+
+ scope.$apply('age = 10');
+
+ changeInputValueTo('');
+ expect(scope.age).toBeNull();
+ expect(inputElm).toBeValid();
+ });
+
+
+ it('should only invalidate the model if suffering from bad input when the data is parsed', function() {
+ compileInput('', {
+ valid: false,
+ badInput: true
+ });
+
+ expect(scope.age).toBeUndefined();
+ expect(inputElm).toBeValid();
+
+ changeInputValueTo('this-will-fail-because-of-the-badInput-flag');
+
+ expect(scope.age).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+
+ it('should validate number if transition from bad input to empty string', function() {
+ var validity = {
+ valid: false,
+ badInput: true
+ };
+ compileInput('', validity);
+ changeInputValueTo('10a');
+ validity.badInput = false;
+ validity.valid = true;
+ changeInputValueTo('');
+ expect(scope.age).toBeNull();
+ expect(inputElm).toBeValid();
+ });
+
+
+ it('should validate with undefined viewValue when $validate() called', function() {
+ compileInput('');
+
+ scope.form.alias.$validate();
+
+ expect(inputElm).toBeValid();
+ expect(scope.form.alias.$error.number).toBeUndefined();
+ });
+
+
+ it('should throw if the model value is not a number', function() {
+ expect(function() {
+ scope.value = 'one';
+ compileInput('');
+ }).toThrowMinErr('ngModel', 'numfmt', "Expected `one` to be a number");
+ });
+
+
+ describe('min', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('1');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+
+ changeInputValueTo('100');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(100);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+
+ it('should validate even if min value changes on-the-fly', function() {
+ scope.min = 10;
+ compileInput('');
+
+ changeInputValueTo('15');
+ expect(inputElm).toBeValid();
+
+ scope.min = 20;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.min = '20';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+ describe('ngMin', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('1');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+
+ changeInputValueTo('100');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(100);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+
+ it('should validate even if the ngMin value changes on-the-fly', function() {
+ scope.min = 10;
+ compileInput('');
+
+ changeInputValueTo('15');
+ expect(inputElm).toBeValid();
+
+ scope.min = 20;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.min = '20';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.min = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+
+ describe('max', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('20');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeUndefined();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+
+ changeInputValueTo('0');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(0);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
+ it('should validate even if max value changes on-the-fly', function() {
+ scope.max = 10;
+ compileInput('');
+
+ changeInputValueTo('5');
+ expect(inputElm).toBeValid();
+
+ scope.max = 0;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.max = '4';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+ describe('ngMax', function() {
+
+ it('should validate', function() {
+ compileInput('');
+
+ changeInputValueTo('20');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeUndefined();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+
+ changeInputValueTo('0');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(0);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
+ it('should validate even if the ngMax value changes on-the-fly', function() {
+ scope.max = 10;
+ compileInput('');
+
+ changeInputValueTo('5');
+ expect(inputElm).toBeValid();
+
+ scope.max = 0;
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = null;
+ scope.$digest();
+ expect(inputElm).toBeValid();
+
+ scope.max = '4';
+ scope.$digest();
+ expect(inputElm).toBeInvalid();
+
+ scope.max = 'abc';
+ scope.$digest();
+ expect(inputElm).toBeValid();
+ });
+ });
+
+
+ describe('required', function() {
+
+ it('should be valid even if value is 0', function() {
+ compileInput('');
+
+ changeInputValueTo('0');
+ expect(inputElm).toBeValid();
+ expect(scope.value).toBe(0);
+ expect(scope.form.alias.$error.required).toBeFalsy();
+ });
+
+ it('should be valid even if value 0 is set from model', function() {
+ compileInput('');
+
+ scope.$apply('value = 0');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('0');
+ expect(scope.form.alias.$error.required).toBeFalsy();
+ });
+
+ it('should register required on non boolean elements', function() {
+ compileInput('
');
+
+ scope.$apply("value = ''");
+
+ expect(inputElm).toBeInvalid();
+ expect(scope.form.alias.$error.required).toBeTruthy();
+ });
+
+ it('should not invalidate number if ng-required=false and viewValue has not been committed', function() {
+ compileInput('');
+
+ scope.$apply("required = false");
+
+ expect(inputElm).toBeValid();
+ });
+ });
+
+ describe('minlength', function() {
+
+ it('should invalidate values that are shorter than the given minlength', function() {
+ compileInput('');
+
+ changeInputValueTo('12');
+ expect(inputElm).toBeInvalid();
+
+ changeInputValueTo('123');
+ expect(inputElm).toBeValid();
+ });
+
+ it('should listen on ng-minlength when minlength is observed', function() {
+ var value = 0;
+ compileInput('');
+ attrs.$observe('minlength', function(v) {
+ value = int(attrs.minlength);
+ });
+
+ scope.$apply(function() {
+ scope.min = 5;
+ });
+
+ expect(value).toBe(5);
+ });
+
+ it('should observe the standard minlength attribute and register it as a validator on the model', function() {
+ compileInput('');
+ scope.$apply(function() {
+ scope.min = 10;
+ });
+
+ changeInputValueTo('12345');
+ expect(inputElm).toBeInvalid();
+ expect(scope.form.input.$error.minlength).toBe(true);
+
+ scope.$apply(function() {
+ scope.min = 5;
+ });
+
+ expect(inputElm).toBeValid();
+ expect(scope.form.input.$error.minlength).not.toBe(true);
+ });
+ });
+
+
+ describe('maxlength', function() {
+
+ it('should invalidate values that are longer than the given maxlength', function() {
+ compileInput('');
+
+ changeInputValueTo('12345678');
+ expect(inputElm).toBeInvalid();
+
+ changeInputValueTo('123');
+ expect(inputElm).toBeValid();
+ });
+
+ it('should listen on ng-maxlength when maxlength is observed', function() {
+ var value = 0;
+ compileInput('');
+ attrs.$observe('maxlength', function(v) {
+ value = int(attrs.maxlength);
+ });
+
+ scope.$apply(function() {
+ scope.max = 10;
+ });
+
+ expect(value).toBe(10);
+ });
+
+ it('should observe the standard maxlength attribute and register it as a validator on the model', function() {
+ compileInput('');
+ scope.$apply(function() {
+ scope.max = 1;
+ });
+
+ changeInputValueTo('12345');
+ expect(inputElm).toBeInvalid();
+ expect(scope.form.input.$error.maxlength).toBe(true);
+
+ scope.$apply(function() {
+ scope.max = 6;
+ });
+
+ expect(inputElm).toBeValid();
+ expect(scope.form.input.$error.maxlength).not.toBe(true);
+ });
+ });
+ });
+
describe('email', function() {
it('should validate e-mail', function() {