diff options
| author | Misko Hevery | 2011-09-08 13:56:29 -0700 | 
|---|---|---|
| committer | Igor Minar | 2011-10-11 11:01:45 -0700 | 
| commit | 4f78fd692c0ec51241476e6be9a4df06cd62fdd6 (patch) | |
| tree | 91f70bb89b9c095126fbc093f51cedbac5cb0c78 /test | |
| parent | df6d2ba3266de405ad6c2f270f24569355706e76 (diff) | |
| download | angular.js-4f78fd692c0ec51241476e6be9a4df06cd62fdd6.tar.bz2 | |
feat(forms): new and improved forms
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>'); | 
