aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorMisko Hevery2011-09-08 13:56:29 -0700
committerIgor Minar2011-10-11 11:01:45 -0700
commit4f78fd692c0ec51241476e6be9a4df06cd62fdd6 (patch)
tree91f70bb89b9c095126fbc093f51cedbac5cb0c78 /test
parentdf6d2ba3266de405ad6c2f270f24569355706e76 (diff)
downloadangular.js-4f78fd692c0ec51241476e6be9a4df06cd62fdd6.tar.bz2
feat(forms): new and improved forms
Diffstat (limited to 'test')
-rw-r--r--test/AngularSpec.js39
-rw-r--r--test/ApiSpecs.js7
-rw-r--r--test/BinderSpec.js135
-rw-r--r--test/BrowserSpecs.js10
-rw-r--r--test/FormattersSpec.js45
-rw-r--r--test/JsonSpec.js4
-rw-r--r--test/ParserSpec.js19
-rw-r--r--test/ScopeSpec.js19
-rw-r--r--test/ValidatorsSpec.js172
-rw-r--r--test/directivesSpec.js11
-rw-r--r--test/jQueryPatchSpec.js57
-rw-r--r--test/jqLiteSpec.js32
-rw-r--r--test/markupSpec.js20
-rw-r--r--test/scenario/dslSpec.js63
-rw-r--r--test/scenario/e2e/widgets.html18
-rw-r--r--test/service/formFactorySpec.js218
-rw-r--r--test/service/invalidWidgetsSpec.js41
-rw-r--r--test/service/routeSpec.js20
-rw-r--r--test/testabilityPatch.js70
-rw-r--r--test/widget/formSpec.js97
-rw-r--r--test/widget/inputSpec.js547
-rw-r--r--test/widget/selectSpec.js510
-rw-r--r--test/widgetsSpec.js820
23 files changed, 1645 insertions, 1329 deletions
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
index 9a1a20c7..0332c01b 100644
--- a/test/AngularSpec.js
+++ b/test/AngularSpec.js
@@ -112,7 +112,6 @@ describe('angular', function(){
});
});
-
describe('size', function() {
it('should return the number of items in an array', function() {
expect(size([])).toBe(0);
@@ -170,6 +169,12 @@ describe('angular', function(){
});
});
+ describe('sortedKeys', function(){
+ it('should collect keys from object', function(){
+ expect(sortedKeys({c:0, b:0, a:0})).toEqual(['a', 'b', 'c']);
+ });
+ });
+
describe('encodeUriSegment', function() {
it('should correctly encode uri segment and not encode chars defined as pchar set in rfc3986',
@@ -322,9 +327,7 @@ describe('angular', function(){
}
};
- expect(angularJsConfig(doc)).toEqual({base_url: '',
- ie_compat: 'angular-ie-compat.js',
- ie_compat_id: 'ng-ie-compat'});
+ expect(angularJsConfig(doc)).toEqual({base_url: ''});
});
@@ -335,16 +338,12 @@ describe('angular', function(){
return [{nodeName: 'SCRIPT',
src: 'angularjs/angular.js',
attributes: [{name: 'ng:autobind', value:'elementIdToCompile'},
- {name: 'ng:css', value: 'css/my_custom_angular.css'},
- {name: 'ng:ie-compat', value: 'myjs/angular-ie-compat.js'},
- {name: 'ng:ie-compat-id', value: 'ngcompat'}] }];
+ {name: 'ng:css', value: 'css/my_custom_angular.css'}] }];
}};
expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
autobind: 'elementIdToCompile',
- css: 'css/my_custom_angular.css',
- ie_compat: 'myjs/angular-ie-compat.js',
- ie_compat_id: 'ngcompat'});
+ css: 'css/my_custom_angular.css'});
});
@@ -357,9 +356,7 @@ describe('angular', function(){
}};
expect(angularJsConfig(doc)).toEqual({autobind: true,
- base_url: 'angularjs/',
- ie_compat_id: 'ng-ie-compat',
- ie_compat: 'angularjs/angular-ie-compat.js'});
+ base_url: 'angularjs/'});
});
@@ -371,9 +368,7 @@ describe('angular', function(){
}};
expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
- autobind: true,
- ie_compat: 'angularjs/angular-ie-compat.js',
- ie_compat_id: 'ng-ie-compat'});
+ autobind: true});
});
@@ -385,9 +380,7 @@ describe('angular', function(){
}};
expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
- autobind: 'foo',
- ie_compat: 'angularjs/angular-ie-compat.js',
- ie_compat_id: 'ng-ie-compat'});
+ autobind: 'foo'});
});
@@ -398,9 +391,7 @@ describe('angular', function(){
src: 'js/angular-0.9.0.js'}];
}};
- expect(angularJsConfig(doc)).toEqual({base_url: 'js/',
- ie_compat: 'js/angular-ie-compat-0.9.0.js',
- ie_compat_id: 'ng-ie-compat'});
+ expect(angularJsConfig(doc)).toEqual({base_url: 'js/'});
});
@@ -411,9 +402,7 @@ describe('angular', function(){
src: 'js/angular-0.9.0-cba23f00.min.js'}];
}};
- expect(angularJsConfig(doc)).toEqual({base_url: 'js/',
- ie_compat: 'js/angular-ie-compat-0.9.0-cba23f00.js',
- ie_compat_id: 'ng-ie-compat'});
+ expect(angularJsConfig(doc)).toEqual({base_url: 'js/'});
});
});
diff --git a/test/ApiSpecs.js b/test/ApiSpecs.js
index 9683a7b7..bd77d734 100644
--- a/test/ApiSpecs.js
+++ b/test/ApiSpecs.js
@@ -15,6 +15,13 @@ describe('api', function() {
expect(map.remove(key)).toBe(value2);
expect(map.get(key)).toBe(undefined);
});
+
+ it('should init from an array', function(){
+ var map = new HashMap(['a','b']);
+ expect(map.get('a')).toBe(0);
+ expect(map.get('b')).toBe(1);
+ expect(map.get('c')).toBe(undefined);
+ });
});
diff --git a/test/BinderSpec.js b/test/BinderSpec.js
index 224c449f..fa7fde60 100644
--- a/test/BinderSpec.js
+++ b/test/BinderSpec.js
@@ -28,56 +28,12 @@ describe('Binder', function(){
}
});
-
- it('text-field should default to value attribute', function(){
- var scope = this.compile('<input type="text" name="model.price" value="abc">');
- scope.$apply();
- assertEquals('abc', scope.model.price);
- });
-
- it('ChangingTextareaUpdatesModel', function(){
- var scope = this.compile('<textarea name="model.note">abc</textarea>');
- scope.$apply();
- assertEquals(scope.model.note, 'abc');
- });
-
- it('ChangingRadioUpdatesModel', function(){
- var scope = this.compile('<div><input type="radio" name="model.price" value="A" checked>' +
- '<input type="radio" name="model.price" value="B"></div>');
- scope.$apply();
- assertEquals(scope.model.price, 'A');
- });
-
- it('ChangingCheckboxUpdatesModel', function(){
- var scope = this.compile('<input type="checkbox" name="model.price" value="true" checked ng:format="boolean"/>');
- assertEquals(true, scope.model.price);
- });
-
it('BindUpdate', function(){
var scope = this.compile('<div ng:init="a=123"/>');
scope.$digest();
assertEquals(123, scope.a);
});
- it('ChangingSelectNonSelectedUpdatesModel', function(){
- var scope = this.compile('<select name="model.price"><option value="A">A</option><option value="B">B</option></select>');
- assertEquals('A', scope.model.price);
- });
-
- it('ChangingMultiselectUpdatesModel', function(){
- var scope = this.compile('<select name="Invoice.options" multiple="multiple">' +
- '<option value="A" selected>Gift wrap</option>' +
- '<option value="B" selected>Extra padding</option>' +
- '<option value="C">Expedite</option>' +
- '</select>');
- assertJsonEquals(["A", "B"], scope.Invoice.options);
- });
-
- it('ChangingSelectSelectedUpdatesModel', function(){
- var scope = this.compile('<select name="model.price"><option>A</option><option selected value="b">B</option></select>');
- assertEquals(scope.model.price, 'b');
- });
-
it('ExecuteInitialization', function(){
var scope = this.compile('<div ng:init="a=123">');
assertEquals(scope.a, 123);
@@ -236,14 +192,13 @@ describe('Binder', function(){
});
it('RepeaterAdd', function(){
- var scope = this.compile('<div><input type="text" name="item.x" ng:repeat="item in items"></div>');
+ var scope = this.compile('<div><input type="text" ng:model="item.x" ng:repeat="item in items"></div>');
scope.items = [{x:'a'}, {x:'b'}];
scope.$apply();
var first = childNode(scope.$element, 1);
var second = childNode(scope.$element, 2);
expect(first.val()).toEqual('a');
expect(second.val()).toEqual('b');
- return
first.val('ABC');
browserTrigger(first, 'keydown');
@@ -440,15 +395,6 @@ describe('Binder', function(){
assertEquals('123{{a}}{{b}}{{c}}', scope.$element.text());
});
- it('RepeaterShouldBindInputsDefaults', function () {
- var scope = this.compile('<div><input value="123" name="item.name" ng:repeat="item in items"></div>');
- scope.items = [{}, {name:'misko'}];
- scope.$apply();
-
- expect(scope.$eval('items[0].name')).toEqual("123");
- expect(scope.$eval('items[1].name')).toEqual("misko");
- });
-
it('ShouldTemplateBindPreElements', function () {
var scope = this.compile('<pre>Hello {{name}}!</pre>');
scope.name = "World";
@@ -459,7 +405,11 @@ describe('Binder', function(){
it('FillInOptionValueWhenMissing', function(){
var scope = this.compile(
- '<select name="foo"><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
+ '<select ng:model="foo">' +
+ '<option selected="true">{{a}}</option>' +
+ '<option value="">{{b}}</option>' +
+ '<option>C</option>' +
+ '</select>');
scope.a = 'A';
scope.b = 'B';
scope.$apply();
@@ -477,52 +427,14 @@ describe('Binder', function(){
expect(optionC.text()).toEqual('C');
});
- it('ValidateForm', function(){
- var scope = this.compile('<div id="test"><input name="name" ng:required>' +
- '<input ng:repeat="item in items" name="item.name" ng:required/></div>',
- jqLite(document.body));
- var items = [{}, {}];
- scope.items = items;
- scope.$apply();
- assertEquals(3, scope.$service('$invalidWidgets').length);
-
- scope.name = '';
- scope.$apply();
- assertEquals(3, scope.$service('$invalidWidgets').length);
-
- scope.name = ' ';
- scope.$apply();
- assertEquals(3, scope.$service('$invalidWidgets').length);
-
- scope.name = 'abc';
- scope.$apply();
- assertEquals(2, scope.$service('$invalidWidgets').length);
-
- items[0].name = 'abc';
- scope.$apply();
- assertEquals(1, scope.$service('$invalidWidgets').length);
-
- items[1].name = 'abc';
- scope.$apply();
- assertEquals(0, scope.$service('$invalidWidgets').length);
- });
-
- it('ValidateOnlyVisibleItems', function(){
- var scope = this.compile('<div><input name="name" ng:required><input ng:show="show" name="name" ng:required></div>', jqLite(document.body));
- scope.show = true;
- scope.$apply();
- assertEquals(2, scope.$service('$invalidWidgets').length);
-
- scope.show = false;
- scope.$apply();
- assertEquals(1, scope.$service('$invalidWidgets').visible());
- });
-
it('DeleteAttributeIfEvaluatesFalse', function(){
var scope = this.compile('<div>' +
- '<input name="a0" ng:bind-attr="{disabled:\'{{true}}\'}"><input name="a1" ng:bind-attr="{disabled:\'{{false}}\'}">' +
- '<input name="b0" ng:bind-attr="{disabled:\'{{1}}\'}"><input name="b1" ng:bind-attr="{disabled:\'{{0}}\'}">' +
- '<input name="c0" ng:bind-attr="{disabled:\'{{[0]}}\'}"><input name="c1" ng:bind-attr="{disabled:\'{{[]}}\'}"></div>');
+ '<input ng:model="a0" ng:bind-attr="{disabled:\'{{true}}\'}">' +
+ '<input ng:model="a1" ng:bind-attr="{disabled:\'{{false}}\'}">' +
+ '<input ng:model="b0" ng:bind-attr="{disabled:\'{{1}}\'}">' +
+ '<input ng:model="b1" ng:bind-attr="{disabled:\'{{0}}\'}">' +
+ '<input ng:model="c0" ng:bind-attr="{disabled:\'{{[0]}}\'}">' +
+ '<input ng:model="c1" ng:bind-attr="{disabled:\'{{[]}}\'}"></div>');
scope.$apply();
function assertChild(index, disabled) {
var child = childNode(scope.$element, index);
@@ -556,8 +468,8 @@ describe('Binder', function(){
it('ItShouldSelectTheCorrectRadioBox', function(){
var scope = this.compile('<div>' +
- '<input type="radio" name="sex" value="female"/>' +
- '<input type="radio" name="sex" value="male"/></div>');
+ '<input type="radio" ng:model="sex" value="female">' +
+ '<input type="radio" ng:model="sex" value="male"></div>');
var female = jqLite(scope.$element[0].childNodes[0]);
var male = jqLite(scope.$element[0].childNodes[1]);
@@ -603,23 +515,4 @@ describe('Binder', function(){
assertEquals("3", scope.$element.text());
});
- it('ItBindHiddenInputFields', function(){
- var scope = this.compile('<input type="hidden" name="myName" value="abc" />');
- scope.$apply();
- assertEquals("abc", scope.myName);
- });
-
- it('ItShouldUseFormaterForText', function(){
- var scope = this.compile('<input name="a" ng:format="list" value="a,b">');
- scope.$apply();
- assertEquals(['a','b'], scope.a);
- var input = scope.$element;
- input[0].value = ' x,,yz';
- browserTrigger(input, 'change');
- assertEquals(['x','yz'], scope.a);
- scope.a = [1 ,2, 3];
- scope.$apply();
- assertEquals('1, 2, 3', input[0].value);
- });
-
});
diff --git a/test/BrowserSpecs.js b/test/BrowserSpecs.js
index de4354a0..692bc5ae 100644
--- a/test/BrowserSpecs.js
+++ b/test/BrowserSpecs.js
@@ -669,7 +669,6 @@ describe('browser', function(){
});
describe('addJs', function() {
-
it('should append a script tag to body', function() {
browser.addJs('http://localhost/bar.js');
expect(scripts.length).toBe(1);
@@ -677,15 +676,6 @@ describe('browser', function(){
expect(scripts[0].id).toBe('');
});
-
- it('should append a script with an id to body', function() {
- browser.addJs('http://localhost/bar.js', 'foo-id');
- expect(scripts.length).toBe(1);
- expect(scripts[0].src).toBe('http://localhost/bar.js');
- expect(scripts[0].id).toBe('foo-id');
- });
-
-
it('should return the appended script element', function() {
var script = browser.addJs('http://localhost/bar.js');
expect(script).toBe(scripts[0]);
diff --git a/test/FormattersSpec.js b/test/FormattersSpec.js
deleted file mode 100644
index 8f438671..00000000
--- a/test/FormattersSpec.js
+++ /dev/null
@@ -1,45 +0,0 @@
-'use strict';
-
-describe("formatter", function(){
- it('should noop', function(){
- assertEquals("abc", angular.formatter.noop.format("abc"));
- assertEquals("xyz", angular.formatter.noop.parse("xyz"));
- assertEquals(null, angular.formatter.noop.parse(null));
- });
-
- it('should List', function() {
- assertEquals('a, b', angular.formatter.list.format(['a', 'b']));
- assertEquals('', angular.formatter.list.format([]));
- assertEquals(['abc', 'c'], angular.formatter.list.parse(" , abc , c ,,"));
- assertEquals([], angular.formatter.list.parse(""));
- assertEquals([], angular.formatter.list.parse(null));
- });
-
- it('should Boolean', function() {
- assertEquals('true', angular.formatter['boolean'].format(true));
- assertEquals('false', angular.formatter['boolean'].format(false));
- assertEquals(true, angular.formatter['boolean'].parse("true"));
- assertEquals(false, angular.formatter['boolean'].parse(""));
- assertEquals(false, angular.formatter['boolean'].parse("false"));
- assertEquals(false, angular.formatter['boolean'].parse(null));
- });
-
- it('should Number', function() {
- assertEquals('1', angular.formatter.number.format(1));
- assertEquals(1, angular.formatter.number.format('1'));
- });
-
- it('should Trim', function() {
- assertEquals('', angular.formatter.trim.format(null));
- assertEquals('', angular.formatter.trim.format(""));
- assertEquals('a', angular.formatter.trim.format(" a "));
- assertEquals('a', angular.formatter.trim.parse(' a '));
- });
-
- describe('json', function(){
- it('should treat empty string as null', function(){
- expect(angular.formatter.json.parse('')).toEqual(null);
- });
- });
-
-});
diff --git a/test/JsonSpec.js b/test/JsonSpec.js
index b0bb15bc..2bd7241f 100644
--- a/test/JsonSpec.js
+++ b/test/JsonSpec.js
@@ -15,6 +15,10 @@ describe('json', function(){
expect(toJson({$$some:'value', 'this':1, '$parent':1}, false)).toEqual('{}');
});
+ it('should not serialize this or $parent', function(){
+ expect(toJson({'this':'value', $parent:'abc'}, false)).toEqual('{}');
+ });
+
it('should serialize strings with escaped characters', function() {
expect(toJson("7\\\"7")).toEqual("\"7\\\\\\\"7\"");
});
diff --git a/test/ParserSpec.js b/test/ParserSpec.js
index a5e1901c..980a673c 100644
--- a/test/ParserSpec.js
+++ b/test/ParserSpec.js
@@ -415,24 +415,6 @@ describe('parser', function() {
expect(scope.$eval('true || run()')).toBe(true);
});
- describe('formatter', function() {
- it('should return no argument function', function() {
- var noop = parser('noop').formatter()();
- expect(noop.format(null, 'abc')).toEqual('abc');
- expect(noop.parse(null, '123')).toEqual('123');
- });
-
- it('should delegate arguments', function() {
- angularFormatter.myArgs = {
- parse: function(a, b){ return [a, b]; },
- format: function(a, b){ return [a, b]; }
- };
- var myArgs = parser('myArgs:objs').formatter()();
- expect(myArgs.format({objs:'B'}, 'A')).toEqual(['A', 'B']);
- expect(myArgs.parse({objs:'D'}, 'C')).toEqual(['C', 'D']);
- delete angularFormatter.myArgs;
- });
- });
describe('assignable', function(){
it('should expose assignment function', function(){
@@ -443,5 +425,4 @@ describe('parser', function() {
expect(scope).toEqual({a:123});
});
});
-
});
diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js
index 492396c5..fa41e5a9 100644
--- a/test/ScopeSpec.js
+++ b/test/ScopeSpec.js
@@ -1,7 +1,7 @@
'use strict';
-describe('Scope', function() {
- var root, mockHandler;
+describe('Scope', function(){
+ var root = null, mockHandler = null;
beforeEach(function() {
root = createScope(angular.service, {
@@ -245,8 +245,14 @@ describe('Scope', function() {
var log = '';
root.a = [];
root.b = {};
- root.$watch('a', function() { log +='.';});
- root.$watch('b', function() { log +='!';});
+ root.$watch('a', function(scope, value){
+ log +='.';
+ expect(value).toBe(root.a);
+ });
+ root.$watch('b', function(scope, value){
+ log +='!';
+ expect(value).toBe(root.b);
+ });
root.$digest();
log = '';
@@ -296,8 +302,8 @@ describe('Scope', function() {
});
- describe('$destroy', function() {
- var first, middle, last, log;
+ describe('$destroy', function(){
+ var first = null, middle = null, last = null, log = null;
beforeEach(function() {
log = '';
@@ -531,7 +537,6 @@ describe('Scope', function() {
greatGrandChild.$on('myEvent', logger);
});
-
it('should bubble event up to the root scope', function() {
grandChild.$emit('myEvent');
expect(log).toEqual('2>1>0>');
diff --git a/test/ValidatorsSpec.js b/test/ValidatorsSpec.js
deleted file mode 100644
index f44a9a59..00000000
--- a/test/ValidatorsSpec.js
+++ /dev/null
@@ -1,172 +0,0 @@
-'use strict';
-
-describe('Validator', function(){
-
- it('ShouldHaveThisSet', function() {
- var validator = {};
- angular.validator.myValidator = function(first, last){
- validator.first = first;
- validator.last = last;
- validator._this = this;
- };
- var scope = compile('<input name="name" ng:validate="myValidator:\'hevery\'"/>')();
- scope.name = 'misko';
- scope.$digest();
- assertEquals('misko', validator.first);
- assertEquals('hevery', validator.last);
- expect(validator._this.$id).toEqual(scope.$id);
- delete angular.validator.myValidator;
- scope.$element.remove();
- });
-
- it('Regexp', function() {
- assertEquals(angular.validator.regexp("abc", /x/, "E1"), "E1");
- assertEquals(angular.validator.regexp("abc", '/x/'),
- "Value does not match expected format /x/.");
- assertEquals(angular.validator.regexp("ab", '^ab$'), null);
- assertEquals(angular.validator.regexp("ab", '^axb$', "E3"), "E3");
- });
-
- it('Number', function() {
- assertEquals(angular.validator.number("ab"), "Not a number");
- assertEquals(angular.validator.number("-0.1",0), "Value can not be less than 0.");
- assertEquals(angular.validator.number("10.1",0,10), "Value can not be greater than 10.");
- assertEquals(angular.validator.number("1.2"), null);
- assertEquals(angular.validator.number(" 1 ", 1, 1), null);
- });
-
- it('Integer', function() {
- assertEquals(angular.validator.integer("ab"), "Not a number");
- assertEquals(angular.validator.integer("1.1"), "Not a whole number");
- assertEquals(angular.validator.integer("1.0"), "Not a whole number");
- assertEquals(angular.validator.integer("1."), "Not a whole number");
- assertEquals(angular.validator.integer("-1",0), "Value can not be less than 0.");
- assertEquals(angular.validator.integer("11",0,10), "Value can not be greater than 10.");
- assertEquals(angular.validator.integer("1"), null);
- assertEquals(angular.validator.integer(" 1 ", 1, 1), null);
- });
-
- it('Date', function() {
- var error = "Value is not a date. (Expecting format: 12/31/2009).";
- expect(angular.validator.date("ab")).toEqual(error);
- expect(angular.validator.date("12/31/2009")).toEqual(null);
- expect(angular.validator.date("1/1/1000")).toEqual(null);
- expect(angular.validator.date("12/31/9999")).toEqual(null);
- expect(angular.validator.date("2/29/2004")).toEqual(null);
- expect(angular.validator.date("2/29/2000")).toEqual(null);
- expect(angular.validator.date("2/29/2100")).toEqual(error);
- expect(angular.validator.date("2/29/2003")).toEqual(error);
- expect(angular.validator.date("41/1/2009")).toEqual(error);
- expect(angular.validator.date("13/1/2009")).toEqual(error);
- expect(angular.validator.date("1/1/209")).toEqual(error);
- expect(angular.validator.date("1/32/2010")).toEqual(error);
- expect(angular.validator.date("001/031/2009")).toEqual(error);
- });
-
- it('Phone', function() {
- var error = "Phone number needs to be in 1(987)654-3210 format in North America " +
- "or +999 (123) 45678 906 internationally.";
- assertEquals(angular.validator.phone("ab"), error);
- assertEquals(null, angular.validator.phone("1(408)757-3023"));
- assertEquals(null, angular.validator.phone("+421 (0905) 933 297"));
- assertEquals(null, angular.validator.phone("+421 0905 933 297"));
- });
-
- it('URL', function() {
- var error = "URL needs to be in http://server[:port]/path format.";
- assertEquals(angular.validator.url("ab"), error);
- assertEquals(angular.validator.url("http://server:123/path"), null);
- });
-
- it('Email', function() {
- var error = "Email needs to be in username@host.com format.";
- assertEquals(error, angular.validator.email("ab"));
- assertEquals(null, angular.validator.email("misko@hevery.com"));
- });
-
- it('Json', function() {
- assertNotNull(angular.validator.json("'"));
- assertNotNull(angular.validator.json("''X"));
- assertNull(angular.validator.json("{}"));
- });
-
- describe('asynchronous', function(){
- var asynchronous = angular.validator.asynchronous;
- var self;
- var value, fn;
-
- beforeEach(function(){
- value = null;
- fn = null;
- self = angular.compile('<input />')();
- jqLite(document.body).append(self.$element);
- self.$element.data('$validate', noop);
- self.$root = self;
- });
-
- afterEach(function(){
- if (self.$element) self.$element.remove();
- });
-
- it('should make a request and show spinner', function(){
- var value, fn;
- var scope = angular.compile(
- '<input type="text" name="name" ng:validate="asynchronous:asyncFn"/>')();
- jqLite(document.body).append(scope.$element);
- var input = scope.$element;
- scope.asyncFn = function(v,f){
- value=v; fn=f;
- };
- scope.name = "misko";
- scope.$digest();
- expect(value).toEqual('misko');
- expect(input.hasClass('ng-input-indicator-wait')).toBeTruthy();
- fn("myError");
- expect(input.hasClass('ng-input-indicator-wait')).toBeFalsy();
- expect(input.attr(NG_VALIDATION_ERROR)).toEqual("myError");
- scope.$element.remove();
- });
-
- it("should not make second request to same value", function(){
- asynchronous.call(self, "kai", function(v,f){value=v; fn=f;});
- expect(value).toEqual('kai');
- expect(self.$service('$invalidWidgets')[0]).toEqual(self.$element);
-
- var spy = jasmine.createSpy();
- asynchronous.call(self, "kai", spy);
- expect(spy).not.toHaveBeenCalled();
-
- asynchronous.call(self, "misko", spy);
- expect(spy).toHaveBeenCalled();
- });
-
- it("should ignore old callbacks, and not remove spinner", function(){
- var firstCb, secondCb;
- asynchronous.call(self, "first", function(v,f){value=v; firstCb=f;});
- asynchronous.call(self, "second", function(v,f){value=v; secondCb=f;});
-
- firstCb();
- expect(self.$element.hasClass('ng-input-indicator-wait')).toBeTruthy();
-
- secondCb();
- expect(self.$element.hasClass('ng-input-indicator-wait')).toBeFalsy();
- });
-
- it("should handle update function", function(){
- var scope = angular.compile(
- '<input name="name" ng:validate="asynchronous:asyncFn:updateFn"/>')();
- scope.asyncFn = jasmine.createSpy();
- scope.updateFn = jasmine.createSpy();
- scope.name = 'misko';
- scope.$digest();
- expect(scope.asyncFn).toHaveBeenCalledWith('misko', scope.asyncFn.mostRecentCall.args[1]);
- assertTrue(scope.$element.hasClass('ng-input-indicator-wait'));
- scope.asyncFn.mostRecentCall.args[1]('myError', {id: 1234, data:'data'});
- assertFalse(scope.$element.hasClass('ng-input-indicator-wait'));
- assertEquals('myError', scope.$element.attr('ng-validation-error'));
- expect(scope.updateFn.mostRecentCall.args[0]).toEqual({id: 1234, data:'data'});
- scope.$element.remove();
- });
-
- });
-});
diff --git a/test/directivesSpec.js b/test/directivesSpec.js
index c925bdb5..1cbb92b0 100644
--- a/test/directivesSpec.js
+++ b/test/directivesSpec.js
@@ -80,6 +80,11 @@ describe("directive", function() {
expect(scope.$element.text()).toEqual('-0false');
});
+ it('should render object as JSON ignore $$', function(){
+ var scope = compile('<div>{{ {key:"value", $$key:"hide"} }}</div>');
+ scope.$digest();
+ expect(fromJson(scope.$element.text())).toEqual({key:'value'});
+ });
});
describe('ng:bind-template', function() {
@@ -103,6 +108,12 @@ describe("directive", function() {
expect(innerText).toEqual('INNER');
});
+ it('should render object as JSON ignore $$', function(){
+ var scope = compile('<pre>{{ {key:"value", $$key:"hide"} }}</pre>');
+ scope.$digest();
+ expect(fromJson(scope.$element.text())).toEqual({key:'value'});
+ });
+
});
describe('ng:bind-attr', function() {
diff --git a/test/jQueryPatchSpec.js b/test/jQueryPatchSpec.js
new file mode 100644
index 00000000..0953bdac
--- /dev/null
+++ b/test/jQueryPatchSpec.js
@@ -0,0 +1,57 @@
+'use strict';
+
+if (window.jQuery) {
+
+ describe('jQuery patch', function(){
+
+ var doc = null;
+ var divSpy = null;
+ var spy1 = null;
+ var spy2 = null;
+
+ beforeEach(function(){
+ divSpy = jasmine.createSpy('div.$destroy');
+ spy1 = jasmine.createSpy('span1.$destroy');
+ spy2 = jasmine.createSpy('span2.$destroy');
+ doc = $('<div><span class=first>abc</span><span class=second>xyz</span></div>');
+ doc.find('span.first').bind('$destroy', spy1);
+ doc.find('span.second').bind('$destroy', spy2);
+ });
+
+ afterEach(function(){
+ expect(divSpy).not.toHaveBeenCalled();
+
+ expect(spy1).toHaveBeenCalled();
+ expect(spy1.callCount).toEqual(1);
+ expect(spy2).toHaveBeenCalled();
+ expect(spy2.callCount).toEqual(1);
+ });
+
+ describe('$detach event', function(){
+
+ it('should fire on detach()', function(){
+ doc.find('span').detach();
+ });
+
+ it('should fire on remove()', function(){
+ doc.find('span').remove();
+ });
+
+ it('should fire on replaceWith()', function(){
+ doc.find('span').replaceWith('<b>bla</b>');
+ });
+
+ it('should fire on replaceAll()', function(){
+ $('<b>bla</b>').replaceAll(doc.find('span'));
+ });
+
+ it('should fire on empty()', function(){
+ doc.empty();
+ });
+
+ it('should fire on html()', function(){
+ doc.html('abc');
+ });
+ });
+ });
+}
diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js
index bb00ca25..28cc7b90 100644
--- a/test/jqLiteSpec.js
+++ b/test/jqLiteSpec.js
@@ -110,6 +110,7 @@ describe('jqLite', function(){
});
});
+
describe('scope', function() {
it('should retrieve scope attached to the current element', function() {
var element = jqLite('<i>foo</i>');
@@ -138,7 +139,7 @@ describe('jqLite', function(){
describe('data', function(){
- it('should set and get ande remove data', function(){
+ it('should set and get and remove data', function(){
var selected = jqLite([a, b, c]);
expect(selected.data('prop', 'value')).toEqual(selected);
@@ -158,6 +159,14 @@ describe('jqLite', function(){
expect(jqLite(b).data('prop')).toEqual(undefined);
expect(jqLite(c).data('prop')).toEqual(undefined);
});
+
+ it('should call $destroy function if element removed', function(){
+ var log = '';
+ var element = jqLite(a);
+ element.bind('$destroy', function(){log+= 'destroy;';});
+ element.remove();
+ expect(log).toEqual('destroy;');
+ });
});
@@ -242,6 +251,21 @@ describe('jqLite', function(){
var selector = jqLite([a, b]);
expect(selector.hasClass('abc')).toEqual(false);
});
+
+
+ it('should make sure that partial class is not checked as a subset', function(){
+ var selector = jqLite([a, b]);
+ selector.addClass('a');
+ selector.addClass('b');
+ selector.addClass('c');
+ expect(selector.addClass('abc')).toEqual(selector);
+ expect(selector.removeClass('abc')).toEqual(selector);
+ expect(jqLite(a).hasClass('abc')).toEqual(false);
+ expect(jqLite(b).hasClass('abc')).toEqual(false);
+ expect(jqLite(a).hasClass('a')).toEqual(true);
+ expect(jqLite(a).hasClass('b')).toEqual(true);
+ expect(jqLite(a).hasClass('c')).toEqual(true);
+ });
});
@@ -318,16 +342,10 @@ describe('jqLite', function(){
describe('removeClass', function(){
it('should allow removal of class', function(){
var selector = jqLite([a, b]);
- selector.addClass('a');
- selector.addClass('b');
- selector.addClass('c');
expect(selector.addClass('abc')).toEqual(selector);
expect(selector.removeClass('abc')).toEqual(selector);
expect(jqLite(a).hasClass('abc')).toEqual(false);
expect(jqLite(b).hasClass('abc')).toEqual(false);
- expect(jqLite(a).hasClass('a')).toEqual(true);
- expect(jqLite(a).hasClass('b')).toEqual(true);
- expect(jqLite(a).hasClass('c')).toEqual(true);
});
diff --git a/test/markupSpec.js b/test/markupSpec.js
index 2704e0dc..bd77c058 100644
--- a/test/markupSpec.js
+++ b/test/markupSpec.js
@@ -26,12 +26,18 @@ describe("markups", function(){
});
it('should translate {{}} in terminal nodes', function(){
- compile('<select name="x"><option value="">Greet {{name}}!</option></select>');
+ compile('<select ng:model="x"><option value="">Greet {{name}}!</option></select>');
scope.$digest();
- expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng:bind-template="Greet {{name}}!">Greet !</option></select>');
+ expect(sortedHtml(element).replace(' selected="true"', '')).
+ toEqual('<select ng:model="x">' +
+ '<option ng:bind-template="Greet {{name}}!">Greet !</option>' +
+ '</select>');
scope.name = 'Misko';
scope.$digest();
- expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng:bind-template="Greet {{name}}!">Greet Misko!</option></select>');
+ expect(sortedHtml(element).replace(' selected="true"', '')).
+ toEqual('<select ng:model="x">' +
+ '<option ng:bind-template="Greet {{name}}!">Greet Misko!</option>' +
+ '</select>');
});
it('should translate {{}} in attributes', function(){
@@ -69,24 +75,24 @@ describe("markups", function(){
it('should populate value attribute on OPTION', function(){
- compile('<select name="x"><option>abc</option></select>');
+ compile('<select ng:model="x"><option>abc</option></select>');
expect(element).toHaveValue('abc');
});
it('should ignore value if already exists', function(){
- compile('<select name="x"><option value="abc">xyz</option></select>');
+ compile('<select ng:model="x"><option value="abc">xyz</option></select>');
expect(element).toHaveValue('abc');
});
it('should set value even if newlines present', function(){
- compile('<select name="x"><option attr="\ntext\n" \n>\nabc\n</option></select>');
+ compile('<select ng:model="x"><option attr="\ntext\n" \n>\nabc\n</option></select>');
expect(element).toHaveValue('\nabc\n');
});
it('should set value even if self closing HTML', function(){
// IE removes the \n from option, which makes this test pointless
if (msie) return;
- compile('<select name="x"><option>\n</option></select>');
+ compile('<select ng:model="x"><option>\n</option></select>');
expect(element).toHaveValue('\n');
});
diff --git a/test/scenario/dslSpec.js b/test/scenario/dslSpec.js
index c5d0a29d..3fc69c14 100644
--- a/test/scenario/dslSpec.js
+++ b/test/scenario/dslSpec.js
@@ -203,29 +203,40 @@ describe("angular.scenario.dsl", function() {
describe('Select', function() {
it('should select single option', function() {
doc.append(
- '<select name="test">' +
- ' <option>A</option>' +
- ' <option selected>B</option>' +
+ '<select ng:model="test">' +
+ ' <option value=A>one</option>' +
+ ' <option value=B selected>two</option>' +
'</select>'
);
$root.dsl.select('test').option('A');
- expect(_jQuery('[name="test"]').val()).toEqual('A');
+ expect(_jQuery('[ng\\:model="test"]').val()).toEqual('A');
+ });
+
+ it('should select option by name', function(){
+ doc.append(
+ '<select ng:model="test">' +
+ ' <option value=A>one</option>' +
+ ' <option value=B selected>two</option>' +
+ '</select>'
+ );
+ $root.dsl.select('test').option('one');
+ expect(_jQuery('[ng\\:model="test"]').val()).toEqual('A');
});
it('should select multiple options', function() {
doc.append(
- '<select name="test" multiple>' +
+ '<select ng:model="test" multiple>' +
' <option>A</option>' +
' <option selected>B</option>' +
' <option>C</option>' +
'</select>'
);
$root.dsl.select('test').options('A', 'B');
- expect(_jQuery('[name="test"]').val()).toEqual(['A','B']);
+ expect(_jQuery('[ng\\:model="test"]').val()).toEqual(['A','B']);
});
it('should fail to select multiple options on non-multiple select', function() {
- doc.append('<select name="test"></select>');
+ doc.append('<select ng:model="test"></select>');
$root.dsl.select('test').options('A', 'B');
expect($root.futureError).toMatch(/did not match/);
});
@@ -477,12 +488,12 @@ describe("angular.scenario.dsl", function() {
it('should prefix selector in $document.elements()', function() {
var chain;
doc.append(
- '<div id="test1"><input name="test.input" value="something"></div>' +
- '<div id="test2"><input name="test.input" value="something"></div>'
+ '<div id="test1"><input ng:model="test.input" value="something"></div>' +
+ '<div id="test2"><input ng:model="test.input" value="something"></div>'
);
chain = $root.dsl.using('div#test2');
chain.input('test.input').enter('foo');
- var inputs = _jQuery('input[name="test.input"]');
+ var inputs = _jQuery('input[ng\\:model="test.input"]');
expect(inputs.first().val()).toEqual('something');
expect(inputs.last().val()).toEqual('foo');
});
@@ -501,10 +512,10 @@ describe("angular.scenario.dsl", function() {
describe('Input', function() {
it('should change value in text input', function() {
- doc.append('<input name="test.input" value="something">');
+ doc.append('<input ng:model="test.input" value="something">');
var chain = $root.dsl.input('test.input');
chain.enter('foo');
- expect(_jQuery('input[name="test.input"]').val()).toEqual('foo');
+ expect(_jQuery('input[ng\\:model="test.input"]').val()).toEqual('foo');
});
it('should return error if no input exists', function() {
@@ -514,16 +525,16 @@ describe("angular.scenario.dsl", function() {
});
it('should toggle checkbox state', function() {
- doc.append('<input type="checkbox" name="test.input" checked>');
- expect(_jQuery('input[name="test.input"]').
+ doc.append('<input type="checkbox" ng:model="test.input" checked>');
+ expect(_jQuery('input[ng\\:model="test.input"]').
prop('checked')).toBe(true);
var chain = $root.dsl.input('test.input');
chain.check();
- expect(_jQuery('input[name="test.input"]').
+ expect(_jQuery('input[ng\\:model="test.input"]').
prop('checked')).toBe(false);
$window.angular.reset();
chain.check();
- expect(_jQuery('input[name="test.input"]').
+ expect(_jQuery('input[ng\\:model="test.input"]').
prop('checked')).toBe(true);
});
@@ -535,20 +546,20 @@ describe("angular.scenario.dsl", function() {
it('should select option from radio group', function() {
doc.append(
- '<input type="radio" name="0@test.input" value="foo">' +
- '<input type="radio" name="0@test.input" value="bar" checked="checked">'
+ '<input type="radio" name="r" ng:model="test.input" value="foo">' +
+ '<input type="radio" name="r" ng:model="test.input" value="bar" checked="checked">'
);
// HACK! We don't know why this is sometimes false on chrome
- _jQuery('input[name="0@test.input"][value="bar"]').prop('checked', true);
- expect(_jQuery('input[name="0@test.input"][value="bar"]').
+ _jQuery('input[ng\\:model="test.input"][value="bar"]').prop('checked', true);
+ expect(_jQuery('input[ng\\:model="test.input"][value="bar"]').
prop('checked')).toBe(true);
- expect(_jQuery('input[name="0@test.input"][value="foo"]').
+ expect(_jQuery('input[ng\\:model="test.input"][value="foo"]').
prop('checked')).toBe(false);
var chain = $root.dsl.input('test.input');
chain.select('foo');
- expect(_jQuery('input[name="0@test.input"][value="bar"]').
+ expect(_jQuery('input[ng\\:model="test.input"][value="bar"]').
prop('checked')).toBe(false);
- expect(_jQuery('input[name="0@test.input"][value="foo"]').
+ expect(_jQuery('input[ng\\:model="test.input"][value="foo"]').
prop('checked')).toBe(true);
});
@@ -560,7 +571,7 @@ describe("angular.scenario.dsl", function() {
describe('val', function() {
it('should return value in text input', function() {
- doc.append('<input name="test.input" value="something">');
+ doc.append('<input ng:model="test.input" value="something">');
$root.dsl.input('test.input').val();
expect($root.futureResult).toEqual("something");
});
@@ -570,10 +581,10 @@ describe("angular.scenario.dsl", function() {
describe('Textarea', function() {
it('should change value in textarea', function() {
- doc.append('<textarea name="test.textarea">something</textarea>');
+ doc.append('<textarea ng:model="test.textarea">something</textarea>');
var chain = $root.dsl.input('test.textarea');
chain.enter('foo');
- expect(_jQuery('textarea[name="test.textarea"]').val()).toEqual('foo');
+ expect(_jQuery('textarea[ng\\:model="test.textarea"]').val()).toEqual('foo');
});
it('should return error if no textarea exists', function() {
diff --git a/test/scenario/e2e/widgets.html b/test/scenario/e2e/widgets.html
index e19a33f4..fb27f72e 100644
--- a/test/scenario/e2e/widgets.html
+++ b/test/scenario/e2e/widgets.html
@@ -15,34 +15,34 @@
<tr>
<td>basic</td>
<td id="text-basic-box">
- <input type="text" name="text.basic"/>
+ <input type="text" ng:model="text.basic"/>
</td>
<td>text.basic={{text.basic}}</td>
</tr>
<tr>
<td>password</td>
- <td><input type="password" name="text.password" /></td>
+ <td><input type="password" ng:model="text.password" /></td>
<td>text.password={{text.password}}</td>
</tr>
<tr>
<td>hidden</td>
- <td><input type="hidden" name="text.hidden" value="hiddenValue" /></td>
+ <td><input type="hidden" ng:model="text.hidden" value="hiddenValue" /></td>
<td>text.hidden={{text.hidden}}</td>
</tr>
<tr><th colspan="3">Input selection field</th></tr>
<tr id="gender-box">
<td>radio</td>
<td>
- <input type="radio" name="gender" value="female"/> Female <br/>
- <input type="radio" name="gender" value="male" checked="checked"/> Male
+ <input type="radio" ng:model="gender" value="female"/> Female <br/>
+ <input type="radio" ng:model="gender" value="male" checked="checked"/> Male
</td>
<td>gender={{gender}}</td>
</tr>
<tr>
<td>checkbox</td>
<td>
- <input type="checkbox" name="checkbox.tea" checked value="on"/> Tea<br/>
- <input type="checkbox" name="checkbox.coffee" value="on"/> Coffe
+ <input type="checkbox" ng:model="checkbox.tea" checked value="on"/> Tea<br/>
+ <input type="checkbox" ng:model="checkbox.coffee" value="on"/> Coffe
</td>
<td>
<pre>checkbox={{checkbox}}</pre>
@@ -51,7 +51,7 @@
<tr>
<td>select</td>
<td>
- <select name="select">
+ <select ng:model="select">
<option>A</option>
<option>B</option>
<option>C</option>
@@ -62,7 +62,7 @@
<tr>
<td>multiselect</td>
<td>
- <select name="multiselect" multiple>
+ <select ng:model="multiselect" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
diff --git a/test/service/formFactorySpec.js b/test/service/formFactorySpec.js
new file mode 100644
index 00000000..5223cede
--- /dev/null
+++ b/test/service/formFactorySpec.js
@@ -0,0 +1,218 @@
+'use strict';
+
+describe('$formFactory', function(){
+
+ var rootScope;
+ var formFactory;
+
+ beforeEach(function(){
+ rootScope = angular.scope();
+ formFactory = rootScope.$service('$formFactory');
+ });
+
+
+ it('should have global form', function(){
+ expect(formFactory.rootForm).toBeTruthy();
+ expect(formFactory.rootForm.$createWidget).toBeTruthy();
+ });
+
+
+ describe('new form', function(){
+ var form;
+ var scope;
+ var log;
+
+ function WidgetCtrl($formFactory){
+ this.$formFactory = $formFactory;
+ log += '<init>';
+ this.$render = function(){
+ log += '$render();';
+ };
+ this.$on('$validate', function(e){
+ log += '$validate();';
+ });
+ }
+
+ WidgetCtrl.$inject = ['$formFactory'];
+
+ WidgetCtrl.prototype = {
+ getFormFactory: function() {
+ return this.$formFactory;
+ }
+ };
+
+ beforeEach(function(){
+ log = '';
+ scope = rootScope.$new();
+ form = formFactory(scope);
+ });
+
+ describe('$createWidget', function(){
+ var widget;
+
+ beforeEach(function() {
+ widget = form.$createWidget({
+ scope:scope,
+ model:'text',
+ alias:'text',
+ controller:WidgetCtrl});
+ });
+
+
+ describe('data flow', function(){
+ it('should have status properties', function(){
+ expect(widget.$error).toEqual({});
+ expect(widget.$valid).toBe(true);
+ expect(widget.$invalid).toBe(false);
+ });
+
+
+ it('should update view when model changes', function(){
+ scope.text = 'abc';
+ scope.$digest();
+ expect(log).toEqual('<init>$validate();$render();');
+ expect(widget.$modelValue).toEqual('abc');
+
+ scope.text = 'xyz';
+ scope.$digest();
+ expect(widget.$modelValue).toEqual('xyz');
+
+ });
+
+
+ it('should have controller prototype methods', function(){
+ expect(widget.getFormFactory()).toEqual(formFactory);
+ });
+ });
+
+
+ describe('validation', function(){
+ it('should update state on error', function(){
+ widget.$emit('$invalid', 'E');
+ expect(widget.$valid).toEqual(false);
+ expect(widget.$invalid).toEqual(true);
+
+ widget.$emit('$valid', 'E');
+ expect(widget.$valid).toEqual(true);
+ expect(widget.$invalid).toEqual(false);
+ });
+
+
+ it('should have called the model setter before the validation', function(){
+ var modelValue;
+ widget.$on('$validate', function(){
+ modelValue = scope.text;
+ });
+ widget.$emit('$viewChange', 'abc');
+ expect(modelValue).toEqual('abc');
+ });
+
+
+ describe('form', function(){
+ it('should invalidate form when widget is invalid', function(){
+ expect(form.$error).toEqual({});
+ expect(form.$valid).toEqual(true);
+ expect(form.$invalid).toEqual(false);
+
+ widget.$emit('$invalid', 'REASON');
+
+ expect(form.$error.REASON).toEqual([widget]);
+ expect(form.$valid).toEqual(false);
+ expect(form.$invalid).toEqual(true);
+
+ var widget2 = form.$createWidget({
+ scope:scope, model:'text',
+ alias:'text',
+ controller:WidgetCtrl
+ });
+ widget2.$emit('$invalid', 'REASON');
+
+ expect(form.$error.REASON).toEqual([widget, widget2]);
+ expect(form.$valid).toEqual(false);
+ expect(form.$invalid).toEqual(true);
+
+ widget.$emit('$valid', 'REASON');
+
+ expect(form.$error.REASON).toEqual([widget2]);
+ expect(form.$valid).toEqual(false);
+ expect(form.$invalid).toEqual(true);
+
+ widget2.$emit('$valid', 'REASON');
+
+ expect(form.$error).toEqual({});
+ expect(form.$valid).toEqual(true);
+ expect(form.$invalid).toEqual(false);
+ });
+ });
+
+ });
+
+ describe('id assignment', function(){
+ it('should default to name expression', function(){
+ expect(form.text).toEqual(widget);
+ });
+
+
+ it('should use ng:id', function() {
+ widget = form.$createWidget({
+ scope:scope,
+ model:'text',
+ alias:'my.id',
+ controller:WidgetCtrl
+ });
+ expect(form['my.id']).toEqual(widget);
+ });
+
+
+ it('should not override existing names', function() {
+ var widget2 = form.$createWidget({
+ scope:scope,
+ model:'text',
+ alias:'text',
+ controller:WidgetCtrl
+ });
+ expect(form.text).toEqual(widget);
+ expect(widget2).not.toEqual(widget);
+ });
+ });
+
+ describe('dealocation', function() {
+ it('should dealocate', function() {
+ var widget2 = form.$createWidget({
+ scope:scope,
+ model:'text',
+ alias:'myId',
+ controller:WidgetCtrl
+ });
+ expect(form.myId).toEqual(widget2);
+ var widget3 = form.$createWidget({
+ scope:scope,
+ model:'text',
+ alias:'myId',
+ controller:WidgetCtrl
+ });
+ expect(form.myId).toEqual(widget2);
+
+ widget3.$destroy();
+ expect(form.myId).toEqual(widget2);
+
+ widget2.$destroy();
+ expect(form.myId).toBeUndefined();
+ });
+
+
+ it('should remove invalid fields from errors, when child widget removed', function(){
+ widget.$emit('$invalid', 'MyError');
+
+ expect(form.$error.MyError).toEqual([widget]);
+ expect(form.$invalid).toEqual(true);
+
+ widget.$destroy();
+
+ expect(form.$error.MyError).toBeUndefined();
+ expect(form.$invalid).toEqual(false);
+ });
+ });
+ });
+ });
+});
diff --git a/test/service/invalidWidgetsSpec.js b/test/service/invalidWidgetsSpec.js
deleted file mode 100644
index fe7efe38..00000000
--- a/test/service/invalidWidgetsSpec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-'use strict';
-
-describe('$invalidWidgets', function() {
- var scope;
-
- beforeEach(function(){
- scope = angular.scope();
- });
-
-
- afterEach(function(){
- dealoc(scope);
- });
-
-
- it("should count number of invalid widgets", function(){
- var element = jqLite('<input name="price" ng:required ng:validate="number">');
- jqLite(document.body).append(element);
- scope = compile(element)();
- var $invalidWidgets = scope.$service('$invalidWidgets');
- expect($invalidWidgets.length).toEqual(1);
-
- scope.price = 123;
- scope.$digest();
- expect($invalidWidgets.length).toEqual(0);
-
- scope.$element.remove();
- scope.price = 'abc';
- scope.$digest();
- expect($invalidWidgets.length).toEqual(0);
-
- jqLite(document.body).append(scope.$element);
- scope.price = 'abcd'; //force revalidation, maybe this should be done automatically?
- scope.$digest();
- expect($invalidWidgets.length).toEqual(1);
-
- jqLite(document.body).html('');
- scope.$digest();
- expect($invalidWidgets.length).toEqual(0);
- });
-});
diff --git a/test/service/routeSpec.js b/test/service/routeSpec.js
index c8c8cbeb..5aba2a1f 100644
--- a/test/service/routeSpec.js
+++ b/test/service/routeSpec.js
@@ -152,18 +152,18 @@ describe('$route', function() {
$location.path('/foo');
scope.$digest();
- expect(scope.$$childHead).toBeTruthy();
- expect(scope.$$childHead).toEqual(scope.$$childTail);
+ expect(scope.$$childHead.$id).toBeTruthy();
+ expect(scope.$$childHead.$id).toEqual(scope.$$childTail.$id);
$location.path('/bar');
scope.$digest();
- expect(scope.$$childHead).toBeTruthy();
- expect(scope.$$childHead).toEqual(scope.$$childTail);
+ expect(scope.$$childHead.$id).toBeTruthy();
+ expect(scope.$$childHead.$id).toEqual(scope.$$childTail.$id);
$location.path('/baz');
scope.$digest();
- expect(scope.$$childHead).toBeTruthy();
- expect(scope.$$childHead).toEqual(scope.$$childTail);
+ expect(scope.$$childHead.$id).toBeTruthy();
+ expect(scope.$$childHead.$id).toEqual(scope.$$childTail.$id);
$location.path('/');
scope.$digest();
@@ -172,6 +172,14 @@ describe('$route', function() {
});
+ it('should infer arguments in injection', function() {
+ $route.when('/test', {controller: function($route){ this.$route = $route; }});
+ $location.path('/test');
+ scope.$digest();
+ expect($route.current.scope.$route).toBe($route);
+ });
+
+
describe('redirection', function() {
it('should support redirection via redirectTo property by updating $location', function() {
var onChangeSpy = jasmine.createSpy('onChange');
diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js
index 3b9d9208..41a6455c 100644
--- a/test/testabilityPatch.js
+++ b/test/testabilityPatch.js
@@ -11,13 +11,17 @@ _jQuery.event.special.change = undefined;
if (window.jstestdriver) {
window.jstd = jstestdriver;
- window.dump = function(){
+ window.dump = function dump(){
var args = [];
forEach(arguments, function(arg){
if (isElement(arg)) {
arg = sortedHtml(arg);
} else if (isObject(arg)) {
- arg = toJson(arg, true);
+ if (arg.$eval == Scope.prototype.$eval) {
+ arg = dumpScope(arg);
+ } else {
+ arg = toJson(arg, true);
+ }
}
args.push(arg);
});
@@ -25,6 +29,23 @@ if (window.jstestdriver) {
};
}
+function dumpScope(scope, offset) {
+ offset = offset || ' ';
+ var log = [offset + 'Scope(' + scope.$id + '): {'];
+ for ( var key in scope ) {
+ if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) {
+ log.push(' ' + key + ': ' + toJson(scope[key]));
+ }
+ }
+ var child = scope.$$childHead;
+ while(child) {
+ log.push(dumpScope(child, offset + ' '));
+ child = child.$$nextSibling;
+ }
+ log.push('}');
+ return log.join('\n' + offset);
+}
+
beforeEach(function(){
// This is to reset parsers global cache of expressions.
compileCache = {};
@@ -36,30 +57,41 @@ beforeEach(function(){
jQuery = _jQuery;
}
+ // This resets global id counter;
+ uid = ['0', '0', '0'];
+
// reset to jQuery or default to us.
bindJQuery();
jqLite(document.body).html('');
- this.addMatchers({
- toBeInvalid: function(){
- var element = jqLite(this.actual);
- var hasClass = element.hasClass('ng-validation-error');
- var validationError = element.attr('ng-validation-error');
- this.message = function(){
- if (!hasClass)
- return "Expected class 'ng-validation-error' not found.";
- return "Expected an error message, but none was found.";
- };
- return hasClass && validationError;
- },
- toBeValid: function(){
+ function cssMatcher(presentClasses, absentClasses) {
+ return function(){
var element = jqLite(this.actual);
- var hasClass = element.hasClass('ng-validation-error');
+ var present = true;
+ var absent = false;
+
+ forEach(presentClasses.split(' '), function(className){
+ present = present && element.hasClass(className);
+ });
+
+ forEach(absentClasses.split(' '), function(className){
+ absent = absent || element.hasClass(className);
+ });
+
this.message = function(){
- return "Expected to not have class 'ng-validation-error' but found.";
+ return "Expected to have " + presentClasses +
+ (absentClasses ? (" and not have " + absentClasses + "" ) : "") +
+ " but had " + element[0].className + ".";
};
- return !hasClass;
- },
+ return present && !absent;
+ };
+ }
+
+ this.addMatchers({
+ toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),
+ toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
+ toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
+ toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
toEqualData: function(expected) {
return equals(this.actual, expected);
diff --git a/test/widget/formSpec.js b/test/widget/formSpec.js
new file mode 100644
index 00000000..7c575c33
--- /dev/null
+++ b/test/widget/formSpec.js
@@ -0,0 +1,97 @@
+'use strict';
+
+describe('form', function(){
+ var doc;
+
+ afterEach(function(){
+ dealoc(doc);
+ });
+
+
+ it('should attach form to DOM', function(){
+ doc = angular.element('<form>');
+ var scope = angular.compile(doc)();
+ expect(doc.data('$form')).toBeTruthy();
+ });
+
+
+ it('should prevent form submission', function(){
+ var startingUrl = '' + window.location;
+ doc = angular.element('<form name="myForm"><input type=submit val=submit>');
+ var scope = angular.compile(doc)();
+ browserTrigger(doc.find('input'));
+ waitsFor(
+ function(){ return true; },
+ 'let browser breath, so that the form submision can manifest itself', 10);
+ runs(function(){
+ expect('' + window.location).toEqual(startingUrl);
+ });
+ });
+
+
+ it('should publish form to scope', function(){
+ doc = angular.element('<form name="myForm">');
+ var scope = angular.compile(doc)();
+ expect(scope.myForm).toBeTruthy();
+ expect(doc.data('$form')).toBeTruthy();
+ expect(doc.data('$form')).toEqual(scope.myForm);
+ });
+
+
+ it('should have ng-valide/ng-invalid style', function(){
+ doc = angular.element('<form name="myForm"><input type=text ng:model=text required>');
+ var scope = angular.compile(doc)();
+ scope.text = 'misko';
+ scope.$digest();
+
+ expect(doc.hasClass('ng-valid')).toBe(true);
+ expect(doc.hasClass('ng-invalid')).toBe(false);
+
+ scope.text = '';
+ scope.$digest();
+ expect(doc.hasClass('ng-valid')).toBe(false);
+ expect(doc.hasClass('ng-invalid')).toBe(true);
+ });
+
+
+ it('should chain nested forms', function(){
+ doc = angular.element('<ng:form name=parent><ng:form name=child><input type=text ng:model=text name=text>');
+ var scope = angular.compile(doc)();
+ var parent = scope.parent;
+ var child = scope.child;
+ var input = child.text;
+
+ input.$emit('$invalid', 'MyError');
+ expect(parent.$error.MyError).toEqual([input]);
+ expect(child.$error.MyError).toEqual([input]);
+
+ input.$emit('$valid', 'MyError');
+ expect(parent.$error.MyError).toBeUndefined();
+ expect(child.$error.MyError).toBeUndefined();
+ });
+
+
+ it('should chain nested forms in repeater', function(){
+ doc = angular.element('<ng:form name=parent>' +
+ '<ng:form ng:repeat="f in forms" name=child><input type=text ng:model=text name=text>');
+ var scope = angular.compile(doc)();
+ scope.forms = [1];
+ scope.$digest();
+
+ var parent = scope.parent;
+ var child = doc.find('input').scope().child;
+ var input = child.text;
+ expect(parent).toBeDefined();
+ expect(child).toBeDefined();
+ expect(input).toBeDefined();
+
+ input.$emit('$invalid', 'myRule');
+ expect(input.$error.myRule).toEqual(true);
+ expect(child.$error.myRule).toEqual([input]);
+ expect(parent.$error.myRule).toEqual([input]);
+
+ input.$emit('$valid', 'myRule');
+ expect(parent.$error.myRule).toBeUndefined();
+ expect(child.$error.myRule).toBeUndefined();
+ });
+});
diff --git a/test/widget/inputSpec.js b/test/widget/inputSpec.js
new file mode 100644
index 00000000..31f8c59c
--- /dev/null
+++ b/test/widget/inputSpec.js
@@ -0,0 +1,547 @@
+'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('<form name="form"><input ' + prefix +
+ 'type="text" ng:model="name" name="name" ng:change="change()"></form>');
+ 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('<form name="form"><input type="abc" ng:model="name"></form>');
+ scope = angular.compile(doc)();
+ expect(doc.find('input').attr('type')).toEqual('text');
+ });
+
+
+ it('should not change html5 types to text', function(){
+ doc = angular.element('<form name="form"><input type="number" ng:model="name"></form>');
+ 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('<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');
+ scope.$service('$browser').defer.flush();
+ expect(scope.name).toEqual('Kai');
+ });
+
+
+ it('should not trigger eval if value does not change', function(){
+ 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');
+ scope.$service('$browser').defer.flush();
+ expect(scope.name).toEqual("Misko");
+ expect(scope.count).toEqual(0);
+ });
+
+
+ it('should allow complex reference binding', function(){
+ 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(){
+ 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);
+ scope.$service('$browser').defer.flush();
+ expect(scope.list).toEqual(['1', '2', '3']);
+ });
+
+
+ it("should render as blank if null", function(){
+ 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(){
+ 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');
+ 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('<input type="list" ng:model="list"/>');
+
+ 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('<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(){
+ 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(){
+ 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" true-value="ano" false-value="nie"/>');
+
+ 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('<input type="text" ng:model="price" name="p" required/>', 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('<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);
+ scope.$service('$browser').defer.flush();
+ expect(element).toBeValid();
+ });
+
+
+ describe('textarea', function(){
+ 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(){
+ 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(){
+ 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(){
+ 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('should ignore text widget which have no name', function(){
+ 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(){
+ 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', function(){
+ expect(function(){
+ compile('<input type="text" ng:model="throw \'\'">');
+ }).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('<input type="@MyType" ng:model="abc">');
+ 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('<input type="@DontExist" ng:model="abc">');
+ 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 = ['<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');
+ 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('<input ng:model="foo" ng:pattern="fooRegexp">'),
+ 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);
+ });
+ });
+});
diff --git a/test/widget/selectSpec.js b/test/widget/selectSpec.js
new file mode 100644
index 00000000..6adf8b93
--- /dev/null
+++ b/test/widget/selectSpec.js
@@ -0,0 +1,510 @@
+'use strict';
+
+describe('select', function(){
+ var compile = null, element = null, scope = null, $formFactory = 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();
+ $formFactory = scope.$service('$formFactory');
+ return scope;
+ };
+ });
+
+ afterEach(function(){
+ dealoc(element);
+ });
+
+
+ describe('select-one', function(){
+
+ it('should compile children of a select without a name, but not create a model for it',
+ function() {
+ compile('<select>' +
+ '<option selected="true">{{a}}</option>' +
+ '<option value="">{{b}}</option>' +
+ '<option>C</option>' +
+ '</select>');
+ scope.a = 'foo';
+ scope.b = 'bar';
+ scope.$digest();
+
+ expect(scope.$element.text()).toBe('foobarC');
+ });
+
+ it('should require', function(){
+ compile('<select name="select" ng:model="selection" required ng:change="log=log+\'change;\'">' +
+ '<option value=""></option>' +
+ '<option value="c">C</option>' +
+ '</select>');
+ scope.log = '';
+ scope.selection = 'c';
+ scope.$digest();
+ expect($formFactory.forElement(element).select.$error.REQUIRED).toEqual(undefined);
+ expect(element).toBeValid();
+ expect(element).toBePristine();
+
+ scope.selection = '';
+ scope.$digest();
+ expect($formFactory.forElement(element).select.$error.REQUIRED).toEqual(true);
+ expect(element).toBeInvalid();
+ expect(element).toBePristine();
+ expect(scope.log).toEqual('');
+
+ element[0].value = 'c';
+ browserTrigger(element, 'change');
+ expect(element).toBeValid();
+ expect(element).toBeDirty();
+ expect(scope.log).toEqual('change;');
+ });
+
+ it('should not be invalid if no require', function(){
+ compile('<select name="select" ng:model="selection">' +
+ '<option value=""></option>' +
+ '<option value="c">C</option>' +
+ '</select>');
+
+ expect(element).toBeValid();
+ expect(element).toBePristine();
+ });
+
+ });
+
+
+ describe('select-multiple', function(){
+ it('should support type="select-multiple"', function(){
+ compile('<select ng:model="selection" multiple>' +
+ '<option>A</option>' +
+ '<option>B</option>' +
+ '</select>');
+ scope.selection = ['A'];
+ scope.$digest();
+ expect(element[0].childNodes[0].selected).toEqual(true);
+ });
+
+ it('should require', function(){
+ compile('<select name="select" ng:model="selection" multiple required>' +
+ '<option>A</option>' +
+ '<option>B</option>' +
+ '</select>');
+
+ scope.selection = [];
+ scope.$digest();
+ expect($formFactory.forElement(element).select.$error.REQUIRED).toEqual(true);
+ expect(element).toBeInvalid();
+ expect(element).toBePristine();
+
+ scope.selection = ['A'];
+ scope.$digest();
+ expect(element).toBeValid();
+ expect(element).toBePristine();
+
+ element[0].value = 'B';
+ browserTrigger(element, 'change');
+ expect(element).toBeValid();
+ expect(element).toBeDirty();
+ });
+
+ });
+
+
+ describe('ng:options', function(){
+ var select, scope;
+
+ function createSelect(attrs, blank, unknown){
+ var html = '<select';
+ forEach(attrs, function(value, key){
+ if (isBoolean(value)) {
+ if (value) html += ' ' + key;
+ } else {
+ html += ' ' + key + '="' + value + '"';
+ }
+ });
+ html += '>' +
+ (blank ? '<option value="">blank</option>' : '') +
+ (unknown ? '<option value="?">unknown</option>' : '') +
+ '</select>';
+ select = jqLite(html);
+ scope = compile(select);
+ }
+
+ function createSingleSelect(blank, unknown){
+ createSelect({
+ 'ng:model':'selected',
+ 'ng:options':'value.name for value in values'
+ }, blank, unknown);
+ }
+
+ function createMultiSelect(blank, unknown){
+ createSelect({
+ 'ng:model':'selected',
+ 'multiple':true,
+ 'ng:options':'value.name for value in values'
+ }, blank, unknown);
+ }
+
+ afterEach(function(){
+ dealoc(select);
+ dealoc(scope);
+ });
+
+ it('should throw when not formated "? for ? in ?"', function(){
+ expect(function(){
+ compile('<select ng:model="selected" ng:options="i dont parse"></select>');
+ }).toThrow("Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in" +
+ " _collection_' but got 'i dont parse'.");
+ });
+
+ it('should render a list', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ var options = select.find('option');
+ expect(options.length).toEqual(3);
+ expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>');
+ expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
+ expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
+ });
+
+ it('should render an object', function(){
+ createSelect({
+ 'ng:model':'selected',
+ 'ng:options': 'value as key for (key, value) in object'
+ });
+ scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
+ scope.selected = scope.object.red;
+ scope.$digest();
+ var options = select.find('option');
+ expect(options.length).toEqual(3);
+ expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>');
+ expect(sortedHtml(options[1])).toEqual('<option value="green">green</option>');
+ expect(sortedHtml(options[2])).toEqual('<option value="red">red</option>');
+ expect(options[2].selected).toEqual(true);
+
+ scope.object.azur = '8888FF';
+ scope.$digest();
+ options = select.find('option');
+ expect(options[3].selected).toEqual(true);
+ });
+
+ it('should grow list', function(){
+ createSingleSelect();
+ scope.values = [];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(1); // because we add special empty option
+ expect(sortedHtml(select.find('option')[0])).toEqual('<option value="?"></option>');
+
+ scope.values.push({name:'A'});
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(1);
+ expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
+
+ scope.values.push({name:'B'});
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
+ expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
+ });
+
+ it('should shrink list', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(3);
+
+ scope.values.pop();
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
+ expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
+
+ scope.values.pop();
+ scope.$digest();
+ expect(select.find('option').length).toEqual(1);
+ expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
+
+ scope.values.pop();
+ scope.selected = null;
+ scope.$digest();
+ expect(select.find('option').length).toEqual(1); // we add back the special empty option
+ });
+
+ it('should shrink and then grow list', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(3);
+
+ scope.values = [{name:'1'}, {name:'2'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+
+ scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(3);
+ });
+
+ it('should update list', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+
+ scope.values = [{name:'B'}, {name:'C'}, {name:'D'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ var options = select.find('option');
+ expect(options.length).toEqual(3);
+ expect(sortedHtml(options[0])).toEqual('<option value="0">B</option>');
+ expect(sortedHtml(options[1])).toEqual('<option value="1">C</option>');
+ expect(sortedHtml(options[2])).toEqual('<option value="2">D</option>');
+ });
+
+ it('should preserve existing options', function(){
+ createSingleSelect(true);
+
+ scope.values = [];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(1);
+
+ scope.values = [{name:'A'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
+ expect(jqLite(select.find('option')[1]).text()).toEqual('A');
+
+ scope.values = [];
+ scope.selected = null;
+ scope.$digest();
+ expect(select.find('option').length).toEqual(1);
+ expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
+ });
+
+ describe('binding', function(){
+ it('should bind to scope value', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}, {name:'B'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+
+ scope.selected = scope.values[1];
+ scope.$digest();
+ expect(select.val()).toEqual('1');
+ });
+
+ it('should bind to scope value and group', function(){
+ createSelect({
+ 'ng:model':'selected',
+ 'ng:options':'item.name group by item.group for item in values'
+ });
+ scope.values = [{name:'A'},
+ {name:'B', group:'first'},
+ {name:'C', group:'second'},
+ {name:'D', group:'first'},
+ {name:'E', group:'second'}];
+ scope.selected = scope.values[3];
+ scope.$digest();
+ expect(select.val()).toEqual('3');
+
+ var first = jqLite(select.find('optgroup')[0]);
+ var b = jqLite(first.find('option')[0]);
+ var d = jqLite(first.find('option')[1]);
+ expect(first.attr('label')).toEqual('first');
+ expect(b.text()).toEqual('B');
+ expect(d.text()).toEqual('D');
+
+ var second = jqLite(select.find('optgroup')[1]);
+ var c = jqLite(second.find('option')[0]);
+ var e = jqLite(second.find('option')[1]);
+ expect(second.attr('label')).toEqual('second');
+ expect(c.text()).toEqual('C');
+ expect(e.text()).toEqual('E');
+
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+ });
+
+ it('should bind to scope value through experession', function(){
+ createSelect({'ng:model':'selected', 'ng:options':'item.id as item.name for item in values'});
+ scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
+ scope.selected = scope.values[0].id;
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+
+ scope.selected = scope.values[1].id;
+ scope.$digest();
+ expect(select.val()).toEqual('1');
+ });
+
+ it('should bind to object key', function(){
+ createSelect({
+ 'ng:model':'selected',
+ 'ng:options':'key as value for (key, value) in object'
+ });
+ scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
+ scope.selected = 'green';
+ scope.$digest();
+ expect(select.val()).toEqual('green');
+
+ scope.selected = 'blue';
+ scope.$digest();
+ expect(select.val()).toEqual('blue');
+ });
+
+ it('should bind to object value', function(){
+ createSelect({
+ 'ng:model':'selected',
+ 'ng:options':'value as key for (key, value) in object'
+ });
+ scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
+ scope.selected = '00FF00';
+ scope.$digest();
+ expect(select.val()).toEqual('green');
+
+ scope.selected = '0000FF';
+ scope.$digest();
+ expect(select.val()).toEqual('blue');
+ });
+
+ it('should insert a blank option if bound to null', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}];
+ scope.selected = null;
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(select.val()).toEqual('');
+ expect(jqLite(select.find('option')[0]).val()).toEqual('');
+
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+ expect(select.find('option').length).toEqual(1);
+ });
+
+ it('should reuse blank option if bound to null', function(){
+ createSingleSelect(true);
+ scope.values = [{name:'A'}];
+ scope.selected = null;
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(select.val()).toEqual('');
+ expect(jqLite(select.find('option')[0]).val()).toEqual('');
+
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+ expect(select.find('option').length).toEqual(2);
+ });
+
+ it('should insert a unknown option if bound to something not in the list', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}];
+ scope.selected = {};
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(select.val()).toEqual('?');
+ expect(jqLite(select.find('option')[0]).val()).toEqual('?');
+
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+ expect(select.find('option').length).toEqual(1);
+ });
+ });
+
+ describe('on change', function(){
+ it('should update model on change', function(){
+ createSingleSelect();
+ scope.values = [{name:'A'}, {name:'B'}];
+ scope.selected = scope.values[0];
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+
+ select.val('1');
+ browserTrigger(select, 'change');
+ expect(scope.selected).toEqual(scope.values[1]);
+ });
+
+ it('should update model on change through expression', function(){
+ createSelect({'ng:model':'selected', 'ng:options':'item.id as item.name for item in values'});
+ scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
+ scope.selected = scope.values[0].id;
+ scope.$digest();
+ expect(select.val()).toEqual('0');
+
+ select.val('1');
+ browserTrigger(select, 'change');
+ expect(scope.selected).toEqual(scope.values[1].id);
+ });
+
+ it('should update model to null on change', function(){
+ createSingleSelect(true);
+ scope.values = [{name:'A'}, {name:'B'}];
+ scope.selected = scope.values[0];
+ select.val('0');
+ scope.$digest();
+
+ select.val('');
+ browserTrigger(select, 'change');
+ expect(scope.selected).toEqual(null);
+ });
+ });
+
+ describe('select-many', function(){
+ it('should read multiple selection', function(){
+ createMultiSelect();
+ scope.values = [{name:'A'}, {name:'B'}];
+
+ scope.selected = [];
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(jqLite(select.find('option')[0]).attr('selected')).toBeFalsy();
+ expect(jqLite(select.find('option')[1]).attr('selected')).toBeFalsy();
+
+ scope.selected.push(scope.values[1]);
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(select.find('option')[0].selected).toEqual(false);
+ expect(select.find('option')[1].selected).toEqual(true);
+
+ scope.selected.push(scope.values[0]);
+ scope.$digest();
+ expect(select.find('option').length).toEqual(2);
+ expect(select.find('option')[0].selected).toEqual(true);
+ expect(select.find('option')[1].selected).toEqual(true);
+ });
+
+ it('should update model on change', function(){
+ createMultiSelect();
+ scope.values = [{name:'A'}, {name:'B'}];
+
+ scope.selected = [];
+ scope.$digest();
+ select.find('option')[0].selected = true;
+
+ browserTrigger(select, 'change');
+ expect(scope.selected).toEqual([scope.values[0]]);
+ });
+ });
+
+ });
+
+});
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
index 02d0ef71..9361d28d 100644
--- a/test/widgetsSpec.js
+++ b/test/widgetsSpec.js
@@ -1,7 +1,7 @@
'use strict';
-describe("widget", function() {
- var compile, element, scope;
+describe("widget", function(){
+ var compile = null, element = null, scope = null;
beforeEach(function() {
scope = null;
@@ -24,397 +24,8 @@ describe("widget", function() {
});
- describe("input", function() {
-
- describe("text", function() {
- it('should input-text auto init and handle keydown/change events', function() {
- compile('<input type="Text" name="name" value="Misko" ng:change="count = count + 1" ng:init="count=0"/>');
- expect(scope.name).toEqual("Misko");
- expect(scope.count).toEqual(0);
-
- 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');
- scope.$service('$browser').defer.flush();
- expect(scope.name).toEqual('Shyam');
- expect(scope.count).toEqual(1);
-
- element.val('Kai');
- browserTrigger(element, 'change');
- expect(scope.name).toEqual('Kai');
- expect(scope.count).toEqual(2);
- });
-
- it('should not trigger eval if value does not change', function() {
- compile('<input type="Text" name="name" value="Misko" ng:change="count = count + 1" ng:init="count=0"/>');
- expect(scope.name).toEqual("Misko");
- expect(scope.count).toEqual(0);
- browserTrigger(element, 'keydown');
- expect(scope.name).toEqual("Misko");
- expect(scope.count).toEqual(0);
- });
-
- it('should allow complex refernce binding', function() {
- compile('<div ng:init="obj={abc:{}}">'+
- '<input type="Text" name="obj[\'abc\'].name" value="Misko""/>'+
- '</div>');
- expect(scope.obj['abc'].name).toEqual('Misko');
- });
-
-
- describe("ng:format", function() {
- it("should format text", function() {
- compile('<input type="Text" name="list" value="a,b,c" ng:format="list"/>');
- expect(scope.list).toEqual(['a', 'b', 'c']);
-
- scope.list = ['x', 'y', 'z'];
- scope.$digest();
- expect(element.val()).toEqual("x, y, z");
-
- element.val('1, 2, 3');
- browserTrigger(element);
- expect(scope.list).toEqual(['1', '2', '3']);
- });
-
- it("should come up blank if null", function() {
- compile('<input type="text" name="age" ng:format="number" ng:init="age=null"/>');
- expect(scope.age).toBeNull();
- expect(scope.$element[0].value).toEqual('');
- });
-
- it("should show incorect text while number does not parse", function() {
- compile('<input type="text" name="age" ng:format="number"/>');
- scope.age = 123;
- scope.$digest();
- scope.$element.val('123X');
- browserTrigger(scope.$element, 'change');
- expect(scope.$element.val()).toEqual('123X');
- expect(scope.age).toEqual(123);
- expect(scope.$element).toBeInvalid();
- });
-
- it("should clober incorect text if model changes", function() {
- compile('<input type="text" name="age" ng:format="number" value="123X"/>');
- scope.age = 456;
- scope.$digest();
- expect(scope.$element.val()).toEqual('456');
- });
-
- it("should not clober text if model changes due to itself", function() {
- compile('<input type="text" name="list" ng:format="list" value="a"/>');
-
- scope.$element.val('a ');
- browserTrigger(scope.$element, 'change');
- expect(scope.$element.val()).toEqual('a ');
- expect(scope.list).toEqual(['a']);
-
- scope.$element.val('a ,');
- browserTrigger(scope.$element, 'change');
- expect(scope.$element.val()).toEqual('a ,');
- expect(scope.list).toEqual(['a']);
-
- scope.$element.val('a , ');
- browserTrigger(scope.$element, 'change');
- expect(scope.$element.val()).toEqual('a , ');
- expect(scope.list).toEqual(['a']);
-
- scope.$element.val('a , b');
- browserTrigger(scope.$element, 'change');
- expect(scope.$element.val()).toEqual('a , b');
- expect(scope.list).toEqual(['a', 'b']);
- });
-
- it("should come up blank when no value specifiend", function() {
- compile('<input type="text" name="age" ng:format="number"/>');
- scope.$digest();
- expect(scope.$element.val()).toEqual('');
- expect(scope.age).toEqual(null);
- });
- });
-
-
- describe("checkbox", function() {
- it("should format booleans", function() {
- compile('<input type="checkbox" name="name" ng:init="name=false"/>');
- expect(scope.name).toEqual(false);
- expect(scope.$element[0].checked).toEqual(false);
- });
-
- it('should support type="checkbox"', function() {
- compile('<input type="checkBox" name="checkbox" checked ng:change="action = true"/>');
- expect(scope.checkbox).toEqual(true);
- browserTrigger(element);
- expect(scope.checkbox).toEqual(false);
- expect(scope.action).toEqual(true);
- browserTrigger(element);
- expect(scope.checkbox).toEqual(true);
- });
-
- it("should use ng:format", function() {
- angularFormatter('testFormat', {
- parse: function(value) {
- return value ? "Worked" : "Failed";
- },
-
- format: function(value) {
- if (value == undefined) return value;
- return value == "Worked";
- }
-
- });
- compile('<input type="checkbox" name="state" ng:format="testFormat" checked/>');
- expect(scope.state).toEqual("Worked");
- expect(scope.$element[0].checked).toEqual(true);
-
- browserTrigger(scope.$element);
- expect(scope.state).toEqual("Failed");
- expect(scope.$element[0].checked).toEqual(false);
-
- scope.state = "Worked";
- scope.$digest();
- expect(scope.state).toEqual("Worked");
- expect(scope.$element[0].checked).toEqual(true);
- });
- });
-
-
- describe("ng:validate", function() {
- it("should process ng:validate", function() {
- compile('<input type="text" name="price" value="abc" ng:validate="number"/>',
- jqLite(document.body));
- expect(element.hasClass('ng-validation-error')).toBeTruthy();
- expect(element.attr('ng-validation-error')).toEqual('Not a number');
-
- scope.price = '123';
- scope.$digest();
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
-
- element.val('x');
- browserTrigger(element);
- expect(element.hasClass('ng-validation-error')).toBeTruthy();
- expect(element.attr('ng-validation-error')).toEqual('Not a number');
- });
-
- it('should not blow up for validation with bound attributes', function() {
- compile('<input type="text" name="price" boo="{{abc}}" ng:required/>');
- expect(element.hasClass('ng-validation-error')).toBeTruthy();
- expect(element.attr('ng-validation-error')).toEqual('Required');
-
- scope.price = '123';
- scope.$digest();
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
- });
-
- it("should not call validator if undefined/empty", function() {
- var lastValue = "NOT_CALLED";
- angularValidator.myValidator = function(value) {lastValue = value;};
- compile('<input type="text" name="url" ng:validate="myValidator"/>');
- expect(lastValue).toEqual("NOT_CALLED");
-
- scope.url = 'http://server';
- scope.$digest();
- expect(lastValue).toEqual("http://server");
-
- delete angularValidator.myValidator;
- });
- });
- });
-
-
- it("should ignore disabled widgets", function() {
- compile('<input type="text" name="price" ng:required disabled/>');
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
- });
-
- it("should ignore readonly widgets", function() {
- compile('<input type="text" name="price" ng:required readonly/>');
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
- });
-
- it("should process ng:required", function() {
- compile('<input type="text" name="price" ng:required/>', jqLite(document.body));
- expect(element.hasClass('ng-validation-error')).toBeTruthy();
- expect(element.attr('ng-validation-error')).toEqual('Required');
-
- scope.price = 'xxx';
- scope.$digest();
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
-
- element.val('');
- browserTrigger(element);
- expect(element.hasClass('ng-validation-error')).toBeTruthy();
- expect(element.attr('ng-validation-error')).toEqual('Required');
- });
-
- it('should allow conditions on ng:required', function() {
- compile('<input type="text" name="price" ng:required="ineedz"/>',
- jqLite(document.body));
- scope.ineedz = false;
- scope.$digest();
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
-
- scope.price = 'xxx';
- scope.$digest();
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
-
- scope.price = '';
- scope.ineedz = true;
- scope.$digest();
- expect(element.hasClass('ng-validation-error')).toBeTruthy();
- expect(element.attr('ng-validation-error')).toEqual('Required');
-
- element.val('abc');
- browserTrigger(element);
- expect(element.hasClass('ng-validation-error')).toBeFalsy();
- expect(element.attr('ng-validation-error')).toBeFalsy();
- });
-
- it("should process ng:required2", function() {
- compile('<textarea name="name">Misko</textarea>');
- expect(scope.name).toEqual("Misko");
-
- scope.name = 'Adam';
- scope.$digest();
- expect(element.val()).toEqual("Adam");
-
- element.val('Shyam');
- browserTrigger(element);
- expect(scope.name).toEqual('Shyam');
-
- element.val('Kai');
- browserTrigger(element);
- expect(scope.name).toEqual('Kai');
- });
-
-
- describe('radio', function() {
- it('should support type="radio"', function() {
- compile('<div>' +
- '<input type="radio" name="chose" value="A" ng:change="clicked = 1"/>' +
- '<input type="radio" name="chose" value="B" checked ng:change="clicked = 2"/>' +
- '<input type="radio" name="chose" value="C" ng:change="clicked = 3"/>' +
- '</div>');
- var a = element[0].childNodes[0];
- var b = element[0].childNodes[1];
- expect(b.name.split('@')[1]).toEqual('chose');
- expect(scope.chose).toEqual('B');
- scope.chose = 'A';
- scope.$digest();
- expect(a.checked).toEqual(true);
-
- scope.chose = 'B';
- scope.$digest();
- expect(a.checked).toEqual(false);
- expect(b.checked).toEqual(true);
- expect(scope.clicked).not.toBeDefined();
-
- browserTrigger(a);
- expect(scope.chose).toEqual('A');
- expect(scope.clicked).toEqual(1);
- });
-
- it('should honor model over html checked keyword after', function() {
- compile('<div ng:init="choose=\'C\'">' +
- '<input type="radio" name="choose" value="A""/>' +
- '<input type="radio" name="choose" value="B" checked/>' +
- '<input type="radio" name="choose" value="C"/>' +
- '</div>');
-
- expect(scope.choose).toEqual('C');
- });
-
- it('should honor model over html checked keyword before', function() {
- compile('<div ng:init="choose=\'A\'">' +
- '<input type="radio" name="choose" value="A""/>' +
- '<input type="radio" name="choose" value="B" checked/>' +
- '<input type="radio" name="choose" value="C"/>' +
- '</div>');
-
- expect(scope.choose).toEqual('A');
- });
-
- });
-
-
- describe('select-one', function() {
- it('should initialize to selected', function() {
- compile(
- '<select name="selection">' +
- '<option>A</option>' +
- '<option selected>B</option>' +
- '</select>');
- expect(scope.selection).toEqual('B');
- scope.selection = 'A';
- scope.$digest();
- expect(scope.selection).toEqual('A');
- expect(element[0].childNodes[0].selected).toEqual(true);
- });
-
- it('should compile children of a select without a name, but not create a model for it',
- function() {
- compile('<select>' +
- '<option selected="true">{{a}}</option>' +
- '<option value="">{{b}}</option>' +
- '<option>C</option>' +
- '</select>');
- scope.a = 'foo';
- scope.b = 'bar';
- scope.$digest();
-
- expect(scope.$element.text()).toBe('foobarC');
- });
- });
-
-
- describe('select-multiple', function() {
- it('should support type="select-multiple"', function() {
- compile('<select name="selection" multiple>' +
- '<option>A</option>' +
- '<option selected>B</option>' +
- '</select>');
- expect(scope.selection).toEqual(['B']);
- scope.selection = ['A'];
- scope.$digest();
- expect(element[0].childNodes[0].selected).toEqual(true);
- });
- });
-
-
- it('should ignore text widget which have no name', function() {
- 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() {
- 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', function() {
- expect(function() {
- compile('<input type="text" name="throw \'\'" value="x"/>');
- }).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
- $logMock.error.logs.shift();
- });
- });
-
-
- describe('ng:switch', function() {
- it('should switch on value change', function() {
+ describe('ng:switch', function(){
+ it('should switch on value change', function(){
compile('<ng:switch on="select">' +
'<div ng:switch-when="1">first:{{name}}</div>' +
'<div ng:switch-when="2">second:{{name}}</div>' +
@@ -458,6 +69,7 @@ describe("widget", function() {
expect(scope.$element.text()).toEqual('works');
dealoc(scope);
});
+
});
@@ -577,428 +189,6 @@ describe("widget", function() {
});
- describe('ng:options', function() {
- var select, scope;
-
- function createSelect(attrs, blank, unknown) {
- var html = '<select';
- forEach(attrs, function(value, key) {
- if (isBoolean(value)) {
- if (value) html += ' ' + key;
- } else {
- html+= ' ' + key + '="' + value + '"';
- }
- });
- html += '>' +
- (blank ? '<option value="">blank</option>' : '') +
- (unknown ? '<option value="?">unknown</option>' : '') +
- '</select>';
- select = jqLite(html);
- scope = compile(select);
- }
-
- function createSingleSelect(blank, unknown) {
- createSelect({
- 'name':'selected',
- 'ng:options':'value.name for value in values'
- }, blank, unknown);
- }
-
- function createMultiSelect(blank, unknown) {
- createSelect({
- 'name':'selected',
- 'multiple':true,
- 'ng:options':'value.name for value in values'
- }, blank, unknown);
- }
-
- afterEach(function() {
- dealoc(select);
- dealoc(scope);
- });
-
- it('should throw when not formated "? for ? in ?"', function() {
- expect(function() {
- compile('<select name="selected" ng:options="i dont parse"></select>');
- }).toThrow("Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in" +
- " _collection_' but got 'i dont parse'.");
- });
-
- it('should render a list', function() {
- createSingleSelect();
- scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
- scope.selected = scope.values[0];
- scope.$digest();
- var options = select.find('option');
- expect(options.length).toEqual(3);
- expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>');
- expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
- expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
- });
-
- it('should render an object', function() {
- createSelect({
- 'name':'selected',
- 'ng:options': 'value as key for (key, value) in object'
- });
- scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
- scope.selected = scope.object.red;
- scope.$digest();
- var options = select.find('option');
- expect(options.length).toEqual(3);
- expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>');
- expect(sortedHtml(options[1])).toEqual('<option value="green">green</option>');
- expect(sortedHtml(options[2])).toEqual('<option value="red">red</option>');
- expect(options[2].selected).toEqual(true);
-
- scope.object.azur = '8888FF';
- scope.$digest();
- options = select.find('option');
- expect(options[3].selected).toEqual(true);
- });
-
- it('should grow list', function() {
- createSingleSelect();
- scope.values = [];
- scope.$digest();
- expect(select.find('option').length).toEqual(1); // because we add special empty option
- expect(sortedHtml(select.find('option')[0])).toEqual('<option value="?"></option>');
-
- scope.values.push({name:'A'});
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.find('option').length).toEqual(1);
- expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
-
- scope.values.push({name:'B'});
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
- expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
- });
-
- it('should shrink list', function() {
- createSingleSelect();
- scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.find('option').length).toEqual(3);
-
- scope.values.pop();
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
- expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
-
- scope.values.pop();
- scope.$digest();
- expect(select.find('option').length).toEqual(1);
- expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
-
- scope.values.pop();
- scope.selected = null;
- scope.$digest();
- expect(select.find('option').length).toEqual(1); // we add back the special empty option
- });
-
- it('should shrink and then grow list', function() {
- createSingleSelect();
- scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.find('option').length).toEqual(3);
-
- scope.values = [{name:'1'}, {name:'2'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
-
- scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.find('option').length).toEqual(3);
- });
-
- it('should update list', function() {
- createSingleSelect();
- scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
- scope.selected = scope.values[0];
- scope.$digest();
-
- scope.values = [{name:'B'}, {name:'C'}, {name:'D'}];
- scope.selected = scope.values[0];
- scope.$digest();
- var options = select.find('option');
- expect(options.length).toEqual(3);
- expect(sortedHtml(options[0])).toEqual('<option value="0">B</option>');
- expect(sortedHtml(options[1])).toEqual('<option value="1">C</option>');
- expect(sortedHtml(options[2])).toEqual('<option value="2">D</option>');
- });
-
- it('should preserve existing options', function() {
- createSingleSelect(true);
-
- scope.$digest();
- expect(select.find('option').length).toEqual(1);
-
- scope.values = [{name:'A'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
- expect(jqLite(select.find('option')[1]).text()).toEqual('A');
-
- scope.values = [];
- scope.selected = null;
- scope.$digest();
- expect(select.find('option').length).toEqual(1);
- expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
- });
-
-
- describe('binding', function() {
- it('should bind to scope value', function() {
- createSingleSelect();
- scope.values = [{name:'A'}, {name:'B'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.val()).toEqual('0');
-
- scope.selected = scope.values[1];
- scope.$digest();
- expect(select.val()).toEqual('1');
- });
-
-
- it('should bind to scope value and group', function() {
- createSelect({
- 'name':'selected',
- 'ng:options':'item.name group by item.group for item in values'
- });
- scope.values = [{name:'A'},
- {name:'B', group:'first'},
- {name:'C', group:'second'},
- {name:'D', group:'first'},
- {name:'E', group:'second'}];
- scope.selected = scope.values[3];
- scope.$digest();
- expect(select.val()).toEqual('3');
-
- var first = jqLite(select.find('optgroup')[0]);
- var b = jqLite(first.find('option')[0]);
- var d = jqLite(first.find('option')[1]);
- expect(first.attr('label')).toEqual('first');
- expect(b.text()).toEqual('B');
- expect(d.text()).toEqual('D');
-
- var second = jqLite(select.find('optgroup')[1]);
- var c = jqLite(second.find('option')[0]);
- var e = jqLite(second.find('option')[1]);
- expect(second.attr('label')).toEqual('second');
- expect(c.text()).toEqual('C');
- expect(e.text()).toEqual('E');
-
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.val()).toEqual('0');
- });
-
- it('should bind to scope value through experession', function() {
- createSelect({'name':'selected', 'ng:options':'item.id as item.name for item in values'});
- scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
- scope.selected = scope.values[0].id;
- scope.$digest();
- expect(select.val()).toEqual('0');
-
- scope.selected = scope.values[1].id;
- scope.$digest();
- expect(select.val()).toEqual('1');
- });
-
- it('should bind to object key', function() {
- createSelect({
- 'name':'selected',
- 'ng:options':'key as value for (key, value) in object'
- });
- scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
- scope.selected = 'green';
- scope.$digest();
- expect(select.val()).toEqual('green');
-
- scope.selected = 'blue';
- scope.$digest();
- expect(select.val()).toEqual('blue');
- });
-
- it('should bind to object value', function() {
- createSelect({
- name:'selected',
- 'ng:options':'value as key for (key, value) in object'
- });
- scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
- scope.selected = '00FF00';
- scope.$digest();
- expect(select.val()).toEqual('green');
-
- scope.selected = '0000FF';
- scope.$digest();
- expect(select.val()).toEqual('blue');
- });
-
- it('should insert a blank option if bound to null', function() {
- createSingleSelect();
- scope.values = [{name:'A'}];
- scope.selected = null;
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(select.val()).toEqual('');
- expect(jqLite(select.find('option')[0]).val()).toEqual('');
-
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.val()).toEqual('0');
- expect(select.find('option').length).toEqual(1);
- });
-
- it('should reuse blank option if bound to null', function() {
- createSingleSelect(true);
- scope.values = [{name:'A'}];
- scope.selected = null;
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(select.val()).toEqual('');
- expect(jqLite(select.find('option')[0]).val()).toEqual('');
-
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.val()).toEqual('0');
- expect(select.find('option').length).toEqual(2);
- });
-
- it('should insert a unknown option if bound to something not in the list', function() {
- createSingleSelect();
- scope.values = [{name:'A'}];
- scope.selected = {};
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(select.val()).toEqual('?');
- expect(jqLite(select.find('option')[0]).val()).toEqual('?');
-
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.val()).toEqual('0');
- expect(select.find('option').length).toEqual(1);
- });
- });
-
-
- describe('on change', function() {
- it('should update model on change', function() {
- createSingleSelect();
- scope.values = [{name:'A'}, {name:'B'}];
- scope.selected = scope.values[0];
- scope.$digest();
- expect(select.val()).toEqual('0');
-
- select.val('1');
- browserTrigger(select, 'change');
- expect(scope.selected).toEqual(scope.values[1]);
- });
-
- it('should fire ng:change if present', function() {
- createSelect({
- name:'selected',
- 'ng:options':'value for value in values',
- 'ng:change':'log = log + selected.name'
- });
- scope.values = [{name:'A'}, {name:'B'}];
- scope.selected = scope.values[0];
- scope.log = '';
- scope.$digest();
- expect(scope.log).toEqual('');
-
- select.val('1');
- browserTrigger(select, 'change');
- expect(scope.log).toEqual('B');
- expect(scope.selected).toEqual(scope.values[1]);
-
- // ignore change event when the model doesn't change
- browserTrigger(select, 'change');
- expect(scope.log).toEqual('B');
- expect(scope.selected).toEqual(scope.values[1]);
-
- select.val('0');
- browserTrigger(select, 'change');
- expect(scope.log).toEqual('BA');
- expect(scope.selected).toEqual(scope.values[0]);
- });
-
- it('should update model on change through expression', function() {
- createSelect({name:'selected', 'ng:options':'item.id as item.name for item in values'});
- scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
- scope.selected = scope.values[0].id;
- scope.$digest();
- expect(select.val()).toEqual('0');
-
- select.val('1');
- browserTrigger(select, 'change');
- expect(scope.selected).toEqual(scope.values[1].id);
- });
-
- it('should update model to null on change', function() {
- createSingleSelect(true);
- scope.values = [{name:'A'}, {name:'B'}];
- scope.selected = scope.values[0];
- select.val('0');
- scope.$digest();
-
- select.val('');
- browserTrigger(select, 'change');
- expect(scope.selected).toEqual(null);
- });
- });
-
-
- describe('select-many', function() {
- it('should read multiple selection', function() {
- createMultiSelect();
- scope.values = [{name:'A'}, {name:'B'}];
-
- scope.selected = [];
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(select.find('option')[0].selected).toBe(false);
- expect(select.find('option')[1].selected).toBe(false);
-
- scope.selected.push(scope.values[1]);
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(select.find('option')[0].selected).toEqual(false);
- expect(select.find('option')[1].selected).toEqual(true);
-
- scope.selected.push(scope.values[0]);
- scope.$digest();
- expect(select.find('option').length).toEqual(2);
- expect(select.find('option')[0].selected).toEqual(true);
- expect(select.find('option')[1].selected).toEqual(true);
- });
-
- it('should update model on change', function() {
- createMultiSelect();
- scope.values = [{name:'A'}, {name:'B'}];
-
- scope.selected = [];
- scope.$digest();
- select.find('option')[0].selected = true;
-
- browserTrigger(select, 'change');
- expect(scope.selected).toEqual([scope.values[0]]);
- });
- });
-
- });
-
-
describe('@ng:repeat', function() {
it('should ng:repeat over array', function() {
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');