angular.js/test/widget/inputSpec.js

597 lines
18 KiB
JavaScript
Raw Normal View History

2011-09-08 20:56:29 +00:00
'use strict';
describe('widget: input', function() {
2011-09-08 20:56:29 +00:00
var compile = null, element = null, scope = null, defer = null;
var $compile = null;
2011-09-08 20:56:29 +00:00
var doc = null;
beforeEach(inject(function($rootScope, $compile, $browser) {
scope = $rootScope;
defer = $browser.defer;
set$compile($compile);
2011-09-08 20:56:29 +00:00
element = null;
compile = function(html, parent) {
if (parent) {
parent.html(html);
element = parent.children();
} else {
element = jqLite(html);
}
$compile(element)(scope);
2011-09-08 20:56:29 +00:00
scope.$apply();
return scope;
};
}));
2011-09-08 20:56:29 +00:00
function set$compile(c) { $compile = c; }
afterEach(function() {
2011-09-08 20:56:29 +00:00
dealoc(element);
dealoc(doc);
});
describe('text', function() {
var form = null,
2011-09-08 20:56:29 +00:00
formElement = null,
inputElement = null;
function createInput(flags){
var prefix = '';
forEach(flags, function(value, key){
prefix += key + '="' + value + '" ';
});
formElement = doc = angular.element('<form name="form"><input ' + prefix +
'type="text" ng:model="name" name="name" ng:change="change()"></form>');
inputElement = formElement.find('input');
$compile(doc)(scope);
2011-09-08 20:56:29 +00:00
form = formElement.inheritedData('$form');
};
it('should bind update scope from model', function() {
2011-09-08 20:56:29 +00:00
createInput();
expect(scope.form.name.$required).toBe(false);
scope.name = 'misko';
scope.$digest();
expect(inputElement.val()).toEqual('misko');
});
it('should require', function() {
2011-09-08 20:56:29 +00:00
createInput({required:''});
expect(scope.form.name.$required).toBe(true);
scope.$digest();
expect(scope.form.name.$valid).toBe(false);
scope.name = 'misko';
scope.$digest();
expect(scope.form.name.$valid).toBe(true);
});
it('should call $destroy on element remove', function() {
2011-09-08 20:56:29 +00:00
createInput();
var log = '';
form.$on('$destroy', function() {
2011-09-08 20:56:29 +00:00
log += 'destroy;';
});
inputElement.remove();
expect(log).toEqual('destroy;');
});
it('should update the model and trim input', function() {
2011-09-08 20:56:29 +00:00
createInput();
var log = '';
scope.change = function() {
2011-09-08 20:56:29 +00:00
log += 'change();';
};
inputElement.val(' a ');
browserTrigger(inputElement);
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.name).toEqual('a');
expect(log).toEqual('change();');
});
it('should change non-html5 types to text', inject(function($rootScope, $compile) {
2011-09-08 20:56:29 +00:00
doc = angular.element('<form name="form"><input type="abc" ng:model="name"></form>');
$compile(doc)($rootScope);
2011-09-08 20:56:29 +00:00
expect(doc.find('input').attr('type')).toEqual('text');
}));
2011-09-08 20:56:29 +00:00
it('should not change html5 types to text', inject(function($rootScope, $compile) {
2011-09-08 20:56:29 +00:00
doc = angular.element('<form name="form"><input type="number" ng:model="name"></form>');
$compile(doc)($rootScope);
2011-09-08 20:56:29 +00:00
expect(doc.find('input')[0].getAttribute('type')).toEqual('number');
}));
2011-09-08 20:56:29 +00:00
});
describe("input", function() {
2011-09-08 20:56:29 +00:00
describe("text", function() {
it('should input-text auto init and handle keydown/change events', function() {
2011-09-08 20:56:29 +00:00
compile('<input type="text" ng:model="name"/>');
scope.name = 'Adam';
scope.$digest();
expect(element.val()).toEqual("Adam");
element.val('Shyam');
browserTrigger(element, 'keydown');
// keydown event must be deferred
expect(scope.name).toEqual('Adam');
defer.flush();
expect(scope.name).toEqual('Shyam');
element.val('Kai');
browserTrigger(element, 'change');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.name).toEqual('Kai');
});
it('should not trigger eval if value does not change', function() {
2011-09-08 20:56:29 +00:00
compile('<input type="text" ng:model="name" ng:change="count = count + 1" ng:init="count=0"/>');
scope.name = 'Misko';
scope.$digest();
expect(scope.name).toEqual("Misko");
expect(scope.count).toEqual(0);
browserTrigger(element, 'keydown');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.name).toEqual("Misko");
expect(scope.count).toEqual(0);
});
it('should allow complex reference binding', function() {
2011-09-08 20:56:29 +00:00
compile('<div>'+
'<input type="text" ng:model="obj[\'abc\'].name"/>'+
'</div>');
scope.obj = { abc: { name: 'Misko'} };
scope.$digest();
expect(scope.$element.find('input').val()).toEqual('Misko');
});
describe("ng:format", function() {
it("should format text", function() {
2011-09-08 20:56:29 +00:00
compile('<input type="list" ng:model="list"/>');
scope.list = ['x', 'y', 'z'];
scope.$digest();
expect(element.val()).toEqual("x, y, z");
element.val('1, 2, 3');
browserTrigger(element);
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.list).toEqual(['1', '2', '3']);
});
it("should render as blank if null", function() {
2011-09-08 20:56:29 +00:00
compile('<input type="text" ng:model="age" ng:format="number" ng:init="age=null"/>');
expect(scope.age).toBeNull();
expect(scope.$element[0].value).toEqual('');
});
it("should show incorrect text while number does not parse", function() {
2011-09-08 20:56:29 +00:00
compile('<input type="number" ng:model="age"/>');
scope.age = 123;
scope.$digest();
expect(scope.$element.val()).toEqual('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.
scope.$element[0].setAttribute('type', 'text');
} catch (e){}
scope.$element.val('123X');
browserTrigger(scope.$element, 'change');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.$element.val()).toEqual('123X');
expect(scope.age).toEqual(123);
expect(scope.$element).toBeInvalid();
});
it("should not clobber text if model changes due to itself", function() {
2011-09-08 20:56:29 +00:00
// When the user types 'a,b' the 'a,' stage parses to ['a'] but if the
// $parseModel function runs it will change to 'a', in essence preventing
// the user from ever typying ','.
compile('<input type="list" ng:model="list"/>');
scope.$element.val('a ');
browserTrigger(scope.$element, 'change');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.$element.val()).toEqual('a ');
expect(scope.list).toEqual(['a']);
scope.$element.val('a ,');
browserTrigger(scope.$element, 'change');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.$element.val()).toEqual('a ,');
expect(scope.list).toEqual(['a']);
scope.$element.val('a , ');
browserTrigger(scope.$element, 'change');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.$element.val()).toEqual('a , ');
expect(scope.list).toEqual(['a']);
scope.$element.val('a , b');
browserTrigger(scope.$element, 'change');
defer.flush();
2011-09-08 20:56:29 +00:00
expect(scope.$element.val()).toEqual('a , b');
expect(scope.list).toEqual(['a', 'b']);
});
it("should come up blank when no value specified", function() {
2011-09-08 20:56:29 +00:00
compile('<input type="number" ng:model="age"/>');
scope.$digest();
expect(scope.$element.val()).toEqual('');
expect(scope.age).toEqual(null);
});
});
describe("checkbox", function() {
it("should format booleans", function() {
2011-09-08 20:56:29 +00:00
compile('<input type="checkbox" ng:model="name" ng:init="name=false"/>');
expect(scope.name).toBe(false);
expect(scope.$element[0].checked).toBe(false);
});
it('should support type="checkbox" with non-standard capitalization', function() {
2011-09-08 20:56:29 +00:00
compile('<input type="checkBox" ng:model="checkbox"/>');
browserTrigger(element);
expect(scope.checkbox).toBe(true);
browserTrigger(element);
expect(scope.checkbox).toBe(false);
});
it('should allow custom enumeration', function() {
compile('<input type="checkbox" ng:model="name" ng:true-value="y" ng:false-value="n">');
2011-09-08 20:56:29 +00:00
scope.name='y';
2011-09-08 20:56:29 +00:00
scope.$digest();
expect(scope.$element[0].checked).toBe(true);
scope.name='n';
2011-09-08 20:56:29 +00:00
scope.$digest();
expect(scope.$element[0].checked).toBe(false);
scope.name='abc';
scope.$digest();
expect(scope.$element[0].checked).toBe(false);
browserTrigger(element);
expect(scope.name).toEqual('y');
2011-09-08 20:56:29 +00:00
browserTrigger(element);
expect(scope.name).toEqual('n');
2011-09-08 20:56:29 +00:00
});
2011-10-12 18:11:10 +00:00
it('should fire ng:change when the value changes', function() {
compile('<input type="checkbox" ng:model="foo" ng:change="changeFn()">');
scope.changeFn = jasmine.createSpy('changeFn');
scope.$digest();
expect(scope.changeFn).not.toHaveBeenCalledOnce();
browserTrigger(element);
expect(scope.changeFn).toHaveBeenCalledOnce();
});
2011-09-08 20:56:29 +00:00
});
});
it("should process required", inject(function($formFactory) {
2011-09-08 20:56:29 +00:00
compile('<input type="text" ng:model="price" name="p" required/>', jqLite(document.body));
expect($formFactory.rootForm.p.$required).toBe(true);
2011-09-08 20:56:29 +00:00
expect(element.hasClass('ng-invalid')).toBeTruthy();
scope.price = 'xxx';
scope.$digest();
expect(element.hasClass('ng-invalid')).toBeFalsy();
element.val('');
browserTrigger(element);
defer.flush();
2011-09-08 20:56:29 +00:00
expect(element.hasClass('ng-invalid')).toBeTruthy();
}));
2011-09-08 20:56:29 +00:00
it('should allow bindings on ng:required', function() {
compile('<input type="text" ng:model="price" ng:required="{{required}}"/>',
jqLite(document.body));
scope.price = '';
scope.required = false;
scope.$digest();
expect(element).toBeValid();
scope.price = 'xxx';
scope.$digest();
expect(element).toBeValid();
scope.price = '';
scope.required = true;
scope.$digest();
expect(element).toBeInvalid();
element.val('abc');
browserTrigger(element);
defer.flush();
2011-09-08 20:56:29 +00:00
expect(element).toBeValid();
});
describe('textarea', function() {
2011-09-08 20:56:29 +00:00
it("should process textarea", function() {
compile('<textarea ng:model="name"></textarea>');
scope.name = 'Adam';
scope.$digest();
expect(element.val()).toEqual("Adam");
element.val('Shyam');
browserTrigger(element);
defer.flush();
expect(scope.name).toEqual('Shyam');
element.val('Kai');
browserTrigger(element);
defer.flush();
expect(scope.name).toEqual('Kai');
});
});
describe('radio', function() {
it('should support type="radio"', function() {
2011-09-08 20:56:29 +00:00
compile('<div>' +
'<input type="radio" name="r" ng:model="chose" value="A"/>' +
'<input type="radio" name="r" ng:model="chose" value="B"/>' +
'<input type="radio" name="r" ng:model="chose" value="C"/>' +
'</div>');
var a = element[0].childNodes[0];
var b = element[0].childNodes[1];
expect(b.name.split('@')[1]).toEqual('r');
scope.chose = 'A';
scope.$digest();
expect(a.checked).toBe(true);
scope.chose = 'B';
scope.$digest();
expect(a.checked).toBe(false);
expect(b.checked).toBe(true);
expect(scope.clicked).not.toBeDefined();
browserTrigger(a);
expect(scope.chose).toEqual('A');
});
it('should honor model over html checked keyword after', function() {
2011-09-08 20:56:29 +00:00
compile('<div ng:init="choose=\'C\'">' +
'<input type="radio" ng:model="choose" value="A""/>' +
'<input type="radio" ng:model="choose" value="B" checked/>' +
'<input type="radio" ng:model="choose" value="C"/>' +
'</div>');
expect(scope.choose).toEqual('C');
var inputs = scope.$element.find('input');
expect(inputs[1].checked).toBe(false);
expect(inputs[2].checked).toBe(true);
});
it('should honor model over html checked keyword before', function() {
2011-09-08 20:56:29 +00:00
compile('<div ng:init="choose=\'A\'">' +
'<input type="radio" ng:model="choose" value="A""/>' +
'<input type="radio" ng:model="choose" value="B" checked/>' +
'<input type="radio" ng:model="choose" value="C"/>' +
'</div>');
expect(scope.choose).toEqual('A');
var inputs = scope.$element.find('input');
expect(inputs[0].checked).toBe(true);
expect(inputs[1].checked).toBe(false);
});
it('it should work with value attribute that is data-bound', function(){
compile(
'<li>'+
'<input ng:repeat="item in [\'a\', \'b\']" ' +
' type="radio" ng:model="choice" value="{{item}}" name="choice">'+
'</li>');
var inputs = scope.$element.find('input');
expect(inputs[0].checked).toBe(false);
expect(inputs[1].checked).toBe(false);
scope.choice = 'b';
scope.$digest();
expect(inputs[0].checked).toBe(false);
expect(inputs[1].checked).toBe(true);
});
2011-09-08 20:56:29 +00:00
});
describe('password', function () {
it('should not change password type to text', function () {
compile('<input type="password" ng:model="name" >');
expect(element.attr('type')).toBe('password');
});
});
it('should ignore text widget which have no name', function() {
2011-09-08 20:56:29 +00:00
compile('<input type="text"/>');
expect(scope.$element.attr('ng-exception')).toBeFalsy();
expect(scope.$element.hasClass('ng-exception')).toBeFalsy();
});
it('should ignore checkbox widget which have no name', function() {
2011-09-08 20:56:29 +00:00
compile('<input type="checkbox"/>');
expect(scope.$element.attr('ng-exception')).toBeFalsy();
expect(scope.$element.hasClass('ng-exception')).toBeFalsy();
});
it('should report error on assignment error', inject(function($log) {
expect(function() {
2011-09-08 20:56:29 +00:00
compile('<input type="text" ng:model="throw \'\'">');
}).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
$log.error.logs.shift();
}));
2011-09-08 20:56:29 +00:00
});
describe('scope declaration', function() {
it('should read the declaration from scope', inject(function($rootScope, $compile, $formFactory) {
var input, formFactory;
var element = angular.element('<input type="@MyType" ng:model="abc">');
$rootScope.MyType = function($f, i) {
2011-09-08 20:56:29 +00:00
input = i;
formFactory = $f;
2011-09-08 20:56:29 +00:00
};
$rootScope.MyType.$inject = ['$formFactory', '$element'];
2011-09-08 20:56:29 +00:00
$compile(element)($rootScope);
2011-09-08 20:56:29 +00:00
expect(formFactory).toBe($formFactory);
2011-09-08 20:56:29 +00:00
expect(input[0]).toBe(element[0]);
}));
2011-09-08 20:56:29 +00:00
it('should throw an error of Controller not declared in scope', inject(function($rootScope, $compile) {
2011-09-08 20:56:29 +00:00
var input, $formFactory;
var element = angular.element('<input type="@DontExist" ng:model="abc">');
2011-09-08 20:56:29 +00:00
var error;
try {
$compile(element)($rootScope);
2011-09-08 20:56:29 +00:00
error = 'no error thrown';
} catch (e) {
error = e;
}
expect(error.message).toEqual("Argument 'DontExist' is not a function, got undefined");
}));
2011-09-08 20:56:29 +00:00
});
describe('text subtypes', function() {
2011-09-08 20:56:29 +00:00
function itShouldVerify(type, validList, invalidList, params, fn) {
describe(type, function() {
2011-09-08 20:56:29 +00:00
forEach(validList, function(value){
it('should validate "' + value + '"', function() {
2011-09-08 20:56:29 +00:00
setup(value);
expect(scope.$element).toBeValid();
});
});
forEach(invalidList, function(value){
it('should NOT validate "' + value + '"', function() {
2011-09-08 20:56:29 +00:00
setup(value);
expect(scope.$element).toBeInvalid();
});
});
function setup(value){
var html = ['<input type="', type.split(' ')[0], '" '];
forEach(params||{}, function(value, key){
html.push(key + '="' + value + '" ');
});
html.push('ng:model="value">');
compile(html.join(''));
(fn||noop)(scope);
scope.value = null;
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.
scope.$element[0].setAttribute('type', 'text');
} catch (e){}
if (value != undefined) {
scope.$element.val(value);
browserTrigger(element, 'keydown');
defer.flush();
2011-09-08 20:56:29 +00:00
}
scope.$digest();
}
});
}
itShouldVerify('email', ['a@b.com'], ['a@B.c']);
itShouldVerify('url', ['http://server:123/path'], ['a@b.c']);
itShouldVerify('number',
['', '1', '12.34', '-4', '+13', '.1'],
['x', '12b', '-6', '101'],
{min:-5, max:100});
itShouldVerify('integer',
[null, '', '1', '12', '-4', '+13'],
['x', '12b', '-6', '101', '1.', '1.2'],
{min:-5, max:100});
itShouldVerify('integer',
[null, '', '0', '1'],
['-1', '2'],
{min:0, max:1});
itShouldVerify('text with inlined pattern constraint',
2011-09-08 20:56:29 +00:00
['', '000-00-0000', '123-45-6789'],
['x000-00-0000x', 'x000-00-0000', '000-00-0000x', 'x'],
2011-09-08 20:56:29 +00:00
{'ng:pattern':'/^\\d\\d\\d-\\d\\d-\\d\\d\\d\\d$/'});
itShouldVerify('text with pattern constraint on scope',
['', '000-00-0000', '123-45-6789'],
['x000-00-0000x', 'x'],
{'ng:pattern':'regexp'}, function(scope){
scope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/;
});
itShouldVerify('text with ng:minlength limit',
['', 'aaa', 'aaaaa', 'aaaaaaaaa'],
['a', 'aa'],
{'ng:minlength': 3});
itShouldVerify('text with ng:maxlength limit',
['', 'a', 'aa', 'aaa'],
['aaaa', 'aaaaa', 'aaaaaaaaa'],
{'ng:maxlength': 3});
it('should throw an error when scope pattern can\'t be found', inject(function($rootScope, $compile) {
var el = jqLite('<input ng:model="foo" ng:pattern="fooRegexp">');
$compile(el)($rootScope);
2011-09-08 20:56:29 +00:00
el.val('xx');
browserTrigger(el, 'keydown');
expect(function() { defer.flush(); }).
2011-09-08 20:56:29 +00:00
toThrow('Expected fooRegexp to be a RegExp but was undefined');
dealoc(el);
}));
2011-09-08 20:56:29 +00:00
});
});