'use strict';
describe('widget: input', function(){
var compile = null, element = null, scope = null, defer = null;
var doc = null;
beforeEach(function() {
scope = null;
element = null;
compile = function(html, parent) {
if (parent) {
parent.html(html);
element = parent.children();
} else {
element = jqLite(html);
}
scope = angular.compile(element)();
scope.$apply();
defer = scope.$service('$browser').defer;
return scope;
};
});
afterEach(function(){
dealoc(element);
dealoc(doc);
});
describe('text', function(){
var scope = null,
form = null,
formElement = null,
inputElement = null;
function createInput(flags){
var prefix = '';
forEach(flags, function(value, key){
prefix += key + '="' + value + '" ';
});
formElement = doc = angular.element('
');
inputElement = formElement.find('input');
scope = angular.compile(doc)();
form = formElement.inheritedData('$form');
};
it('should bind update scope from model', function(){
createInput();
expect(scope.form.name.$required).toBe(false);
scope.name = 'misko';
scope.$digest();
expect(inputElement.val()).toEqual('misko');
});
it('should require', function(){
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(){
createInput();
var log = '';
form.$on('$destroy', function(){
log += 'destroy;';
});
inputElement.remove();
expect(log).toEqual('destroy;');
});
it('should update the model and trim input', function(){
createInput();
var log = '';
scope.change = function(){
log += 'change();';
};
inputElement.val(' a ');
browserTrigger(inputElement);
scope.$service('$browser').defer.flush();
expect(scope.name).toEqual('a');
expect(log).toEqual('change();');
});
it('should change non-html5 types to text', function(){
doc = angular.element('');
scope = angular.compile(doc)();
expect(doc.find('input').attr('type')).toEqual('text');
});
it('should not change html5 types to text', function(){
doc = angular.element('');
scope = angular.compile(doc)();
expect(doc.find('input')[0].getAttribute('type')).toEqual('number');
});
});
describe("input", function(){
describe("text", function(){
it('should input-text auto init and handle keydown/change events', function(){
compile('');
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');
scope.$service('$browser').defer.flush();
expect(scope.name).toEqual('Kai');
});
it('should not trigger eval if value does not change', function(){
compile('');
scope.name = 'Misko';
scope.$digest();
expect(scope.name).toEqual("Misko");
expect(scope.count).toEqual(0);
browserTrigger(element, 'keydown');
scope.$service('$browser').defer.flush();
expect(scope.name).toEqual("Misko");
expect(scope.count).toEqual(0);
});
it('should allow complex reference binding', function(){
compile('
'+
''+
'
');
scope.obj = { abc: { name: 'Misko'} };
scope.$digest();
expect(scope.$element.find('input').val()).toEqual('Misko');
});
describe("ng:format", function(){
it("should format text", function(){
compile('');
scope.list = ['x', 'y', 'z'];
scope.$digest();
expect(element.val()).toEqual("x, y, z");
element.val('1, 2, 3');
browserTrigger(element);
scope.$service('$browser').defer.flush();
expect(scope.list).toEqual(['1', '2', '3']);
});
it("should render as blank if null", function(){
compile('');
expect(scope.age).toBeNull();
expect(scope.$element[0].value).toEqual('');
});
it("should show incorrect text while number does not parse", function(){
compile('');
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');
scope.$service('$browser').defer.flush();
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(){
// 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('');
scope.$element.val('a ');
browserTrigger(scope.$element, 'change');
scope.$service('$browser').defer.flush();
expect(scope.$element.val()).toEqual('a ');
expect(scope.list).toEqual(['a']);
scope.$element.val('a ,');
browserTrigger(scope.$element, 'change');
scope.$service('$browser').defer.flush();
expect(scope.$element.val()).toEqual('a ,');
expect(scope.list).toEqual(['a']);
scope.$element.val('a , ');
browserTrigger(scope.$element, 'change');
scope.$service('$browser').defer.flush();
expect(scope.$element.val()).toEqual('a , ');
expect(scope.list).toEqual(['a']);
scope.$element.val('a , b');
browserTrigger(scope.$element, 'change');
scope.$service('$browser').defer.flush();
expect(scope.$element.val()).toEqual('a , b');
expect(scope.list).toEqual(['a', 'b']);
});
it("should come up blank when no value specified", function(){
compile('');
scope.$digest();
expect(scope.$element.val()).toEqual('');
expect(scope.age).toEqual(null);
});
});
describe("checkbox", function(){
it("should format booleans", function(){
compile('');
expect(scope.name).toBe(false);
expect(scope.$element[0].checked).toBe(false);
});
it('should support type="checkbox" with non-standard capitalization', function(){
compile('');
browserTrigger(element);
expect(scope.checkbox).toBe(true);
browserTrigger(element);
expect(scope.checkbox).toBe(false);
});
it('should allow custom enumeration', function(){
compile('');
scope.name='ano';
scope.$digest();
expect(scope.$element[0].checked).toBe(true);
scope.name='nie';
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('ano');
browserTrigger(element);
expect(scope.name).toEqual('nie');
});
});
});
it("should process required", function(){
compile('', jqLite(document.body));
expect(scope.$service('$formFactory').rootForm.p.$required).toBe(true);
expect(element.hasClass('ng-invalid')).toBeTruthy();
scope.price = 'xxx';
scope.$digest();
expect(element.hasClass('ng-invalid')).toBeFalsy();
element.val('');
browserTrigger(element);
scope.$service('$browser').defer.flush();
expect(element.hasClass('ng-invalid')).toBeTruthy();
});
it('should allow bindings on ng:required', function() {
compile('',
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);
scope.$service('$browser').defer.flush();
expect(element).toBeValid();
});
describe('textarea', function(){
it("should process textarea", function() {
compile('');
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(){
compile('
' +
'' +
'' +
'' +
'
');
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(){
compile('
' +
'' +
'' +
'' +
'
');
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(){
compile('
' +
'' +
'' +
'' +
'
');
expect(scope.choose).toEqual('A');
var inputs = scope.$element.find('input');
expect(inputs[0].checked).toBe(true);
expect(inputs[1].checked).toBe(false);
});
});
it('should ignore text widget which have no name', function(){
compile('');
expect(scope.$element.attr('ng-exception')).toBeFalsy();
expect(scope.$element.hasClass('ng-exception')).toBeFalsy();
});
it('should ignore checkbox widget which have no name', function(){
compile('');
expect(scope.$element.attr('ng-exception')).toBeFalsy();
expect(scope.$element.hasClass('ng-exception')).toBeFalsy();
});
it('should report error on assignment error', function(){
expect(function(){
compile('');
}).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
$logMock.error.logs.shift();
});
});
describe('scope declaration', function(){
it('should read the declaration from scope', function(){
var input, $formFactory;
element = angular.element('');
scope = angular.scope();
scope.MyType = function($f, i) {
input = i;
$formFactory = $f;
};
scope.MyType.$inject = ['$formFactory'];
angular.compile(element)(scope);
expect($formFactory).toBe(scope.$service('$formFactory'));
expect(input[0]).toBe(element[0]);
});
it('should throw an error of Cntoroller not declared in scope', function() {
var input, $formFactory;
element = angular.element('');
var error;
try {
scope = angular.scope();
angular.compile(element)(scope);
error = 'no error thrown';
} catch (e) {
error = e;
}
expect(error.message).toEqual("Argument 'DontExist' is not a function, got undefined");
});
});
describe('text subtypes', function(){
function itShouldVerify(type, validList, invalidList, params, fn) {
describe(type, function(){
forEach(validList, function(value){
it('should validate "' + value + '"', function(){
setup(value);
expect(scope.$element).toBeValid();
});
});
forEach(invalidList, function(value){
it('should NOT validate "' + value + '"', function(){
setup(value);
expect(scope.$element).toBeInvalid();
});
});
function setup(value){
var html = ['');
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');
scope.$service('$browser').defer.flush();
}
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 contraint',
['', '000-00-0000', '123-45-6789'],
['x000-00-0000x', 'x'],
{'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$/;
});
it('should throw an error when scope pattern can\'t be found', function() {
var el = jqLite(''),
scope = angular.compile(el)();
el.val('xx');
browserTrigger(el, 'keydown');
expect(function() { scope.$service('$browser').defer.flush(); }).
toThrow('Expected fooRegexp to be a RegExp but was undefined');
dealoc(el);
});
});
});