diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/AngularSpec.js | 39 | ||||
| -rw-r--r-- | test/ApiSpecs.js | 7 | ||||
| -rw-r--r-- | test/BinderSpec.js | 135 | ||||
| -rw-r--r-- | test/BrowserSpecs.js | 10 | ||||
| -rw-r--r-- | test/FormattersSpec.js | 45 | ||||
| -rw-r--r-- | test/JsonSpec.js | 4 | ||||
| -rw-r--r-- | test/ParserSpec.js | 19 | ||||
| -rw-r--r-- | test/ScopeSpec.js | 19 | ||||
| -rw-r--r-- | test/ValidatorsSpec.js | 172 | ||||
| -rw-r--r-- | test/directivesSpec.js | 11 | ||||
| -rw-r--r-- | test/jQueryPatchSpec.js | 57 | ||||
| -rw-r--r-- | test/jqLiteSpec.js | 32 | ||||
| -rw-r--r-- | test/markupSpec.js | 20 | ||||
| -rw-r--r-- | test/scenario/dslSpec.js | 63 | ||||
| -rw-r--r-- | test/scenario/e2e/widgets.html | 18 | ||||
| -rw-r--r-- | test/service/formFactorySpec.js | 218 | ||||
| -rw-r--r-- | test/service/invalidWidgetsSpec.js | 41 | ||||
| -rw-r--r-- | test/service/routeSpec.js | 20 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 70 | ||||
| -rw-r--r-- | test/widget/formSpec.js | 97 | ||||
| -rw-r--r-- | test/widget/inputSpec.js | 547 | ||||
| -rw-r--r-- | test/widget/selectSpec.js | 510 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 820 |
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>'); |
