diff options
| author | Vojta Jina | 2012-02-15 17:16:02 -0800 | 
|---|---|---|
| committer | Vojta Jina | 2012-02-28 17:46:58 -0800 | 
| commit | 21c725f1a12d1de758cab6e4c4fafc5c420eb565 (patch) | |
| tree | 4d1b362387de2c41748a63b5baee0f18c3c8e5ec /test/widget/selectSpec.js | |
| parent | e23fa768aaf6d1d966c335979fe8316330c2fe28 (diff) | |
| download | angular.js-21c725f1a12d1de758cab6e4c4fafc5c420eb565.tar.bz2 | |
refactor(forms): Even better forms
- remove $formFactory completely
- remove parallel scope hierarchy (forms, widgets)
- use new compiler features (widgets, forms are controllers)
- any directive can add formatter/parser (validators, convertors)
Breaks no custom input types
Breaks removed integer input type
Breaks remove list input type (ng-list directive instead)
Breaks inputs bind only blur event by default (added ng:bind-change directive)
Diffstat (limited to 'test/widget/selectSpec.js')
| -rw-r--r-- | test/widget/selectSpec.js | 806 | 
1 files changed, 499 insertions, 307 deletions
| diff --git a/test/widget/selectSpec.js b/test/widget/selectSpec.js index 00bc2192..9db47c05 100644 --- a/test/widget/selectSpec.js +++ b/test/widget/selectSpec.js @@ -1,60 +1,69 @@  'use strict';  describe('select', function() { -  var compile = null, element = null, scope = null; +  var scope, formElement, element, $compile; -  beforeEach(inject(function($compile, $rootScope) { +  function compile(html) { +    formElement = jqLite('<form name="form">' + html + '</form>'); +    element = formElement.find('select'); +    $compile(formElement)(scope); +    scope.$apply(); +  } + +  beforeEach(inject(function($injector, $rootScope) {      scope = $rootScope; -    element = null; -    compile = function(html, parent) { -      if (parent) { -        parent.html(html); -        element = parent.children(); -      } else { -        element = jqLite(html); -      } -      element = $compile(element)($rootScope); -      scope.$apply(); -      return scope; -    }; +    $compile = $injector.get('$compile'); +    formElement = element = null;    }));    afterEach(function() { -    dealoc(element); +    dealoc(formElement);    });    describe('select-one', function() { -    it('should compile children of a select without a name, but not create a model for it', +    it('should compile children of a select without a ng:model, 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(); +      scope.$apply(function() { +        scope.a = 'foo'; +        scope.b = 'bar'; +      });        expect(element.text()).toBe('foobarC');      }); -    it('should require', inject(function($formFactory) { -      compile('<select name="select" ng:model="selection" required ng:change="log=log+\'change;\'">' + + +    it('should require', function() { +      compile( +        '<select name="select" ng:model="selection" required ng:change="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); + +      scope.change = function() { +        scope.log += 'change;'; +      }; + +      scope.$apply(function() { +        scope.log = ''; +        scope.selection = 'c'; +      }); + +      expect(scope.form.select.error.REQUIRED).toBeFalsy();        expect(element).toBeValid();        expect(element).toBePristine(); -      scope.selection = ''; -      scope.$digest(); -      expect($formFactory.forElement(element).select.$error.REQUIRED).toEqual(true); +      scope.$apply(function() { +        scope.selection = ''; +      }); + +      expect(scope.form.select.error.REQUIRED).toBeTruthy();        expect(element).toBeInvalid();        expect(element).toBePristine();        expect(scope.log).toEqual(''); @@ -64,10 +73,12 @@ describe('select', function() {        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">' + +      compile( +        '<select name="select" ng:model="selection">' +            '<option value=""></option>' +            '<option value="c">C</option>' +          '</select>'); @@ -75,35 +86,45 @@ describe('select', function() {        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(); +      compile( +        '<select ng:model="selection" multiple>' + +          '<option>A</option>' + +          '<option>B</option>' + +        '</select>'); + +      scope.$apply(function() { +        scope.selection = ['A']; +      }); +        expect(element[0].childNodes[0].selected).toEqual(true);      }); -    it('should require', inject(function($formFactory) { -      compile('<select name="select" ng:model="selection" multiple required>' + + +    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); +      scope.$apply(function() { +        scope.selection = []; +      }); + +      expect(scope.form.select.error.REQUIRED).toBeTruthy();        expect(element).toBeInvalid();        expect(element).toBePristine(); -      scope.selection = ['A']; -      scope.$digest(); +      scope.$apply(function() { +        scope.selection = ['A']; +      }); +        expect(element).toBeValid();        expect(element).toBePristine(); @@ -111,17 +132,14 @@ describe('select', function() {        browserTrigger(element, 'change');        expect(element).toBeValid();        expect(element).toBeDirty(); -    })); - +    });    });    describe('ng:options', function() { -    var select, scope; - -    function createSelect(attrs, blank, unknown){ +    function createSelect(attrs, blank, unknown) {        var html = '<select'; -      forEach(attrs, function(value, key){ +      forEach(attrs, function(value, key) {          if (isBoolean(value)) {            if (value) html += ' ' + key;          } else { @@ -132,18 +150,18 @@ describe('select', function() {          (blank ? (isString(blank) ? blank : '<option value="">blank</option>') : '') +          (unknown ? (isString(unknown) ? unknown : '<option value="?">unknown</option>') : '') +        '</select>'; -      select = jqLite(html); -      scope = compile(select); + +      compile(html);      } -    function createSingleSelect(blank, unknown){ +    function createSingleSelect(blank, unknown) {        createSelect({          'ng:model':'selected',          'ng:options':'value.name for value in values'        }, blank, unknown);      } -    function createMultiSelect(blank, unknown){ +    function createMultiSelect(blank, unknown) {        createSelect({          'ng:model':'selected',          'multiple':true, @@ -151,366 +169,474 @@ describe('select', function() {        }, blank, unknown);      } -    afterEach(function() { -      dealoc(select); -      dealoc(scope); -    }); -    it('should throw when not formated "? for ? in ?"', inject(function($rootScope, $exceptionHandler) { +    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'); + +      scope.$apply(function() { +        scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}]; +        scope.selected = scope.values[0]; +      }); + +      var options = element.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: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'); + +      scope.$apply(function() { +        scope.object = {'red': 'FF0000', 'green': '00FF00', 'blue': '0000FF'}; +        scope.selected = scope.object.red; +      }); + +      var options = element.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'); +      scope.$apply(function() { +        scope.object.azur = '8888FF'; +      }); + +      options = element.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>'); + +      scope.$apply(function() { +        scope.values = []; +      }); + +      expect(element.find('option').length).toEqual(1); // because we add special empty option +      expect(sortedHtml(element.find('option')[0])).toEqual('<option value="?"></option>'); + +      scope.$apply(function() { +        scope.values.push({name:'A'}); +        scope.selected = scope.values[0]; +      }); + +      expect(element.find('option').length).toEqual(1); +      expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>'); + +      scope.$apply(function() { +        scope.values.push({name:'B'}); +      }); + +      expect(element.find('option').length).toEqual(2); +      expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>'); +      expect(sortedHtml(element.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 + +      scope.$apply(function() { +        scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; +        scope.selected = scope.values[0]; +      }); + +      expect(element.find('option').length).toEqual(3); + +      scope.$apply(function() { +        scope.values.pop(); +      }); + +      expect(element.find('option').length).toEqual(2); +      expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>'); +      expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>'); + +      scope.$apply(function() { +        scope.values.pop(); +      }); + +      expect(element.find('option').length).toEqual(1); +      expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>'); + +      scope.$apply(function() { +        scope.values.pop(); +        scope.selected = null; +      }); + +      expect(element.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); + +      scope.$apply(function() { +        scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; +        scope.selected = scope.values[0]; +      }); + +      expect(element.find('option').length).toEqual(3); + +      scope.$apply(function() { +        scope.values = [{name: '1'}, {name: '2'}]; +        scope.selected = scope.values[0]; +      }); + +      expect(element.find('option').length).toEqual(2); + +      scope.$apply(function() { +        scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}]; +        scope.selected = scope.values[0]; +      }); + +      expect(element.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'); + +      scope.$apply(function() { +        scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}]; +        scope.selected = scope.values[0]; +      }); + +      scope.$apply(function() { +        scope.values = [{name: 'B'}, {name: 'C'}, {name: 'D'}]; +        scope.selected = scope.values[0]; +      }); + +      var options = element.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'); +      scope.$apply(function() { +        scope.values = []; +      }); + +      expect(element.find('option').length).toEqual(1); + +      scope.$apply(function() { +        scope.values = [{name: 'A'}]; +        scope.selected = scope.values[0]; +      }); + +      expect(element.find('option').length).toEqual(2); +      expect(jqLite(element.find('option')[0]).text()).toEqual('blank'); +      expect(jqLite(element.find('option')[1]).text()).toEqual('A'); + +      scope.$apply(function() { +        scope.values = []; +        scope.selected = null; +      }); + +      expect(element.find('option').length).toEqual(1); +      expect(jqLite(element.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'); +        scope.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = scope.values[0]; +        }); + +        expect(element.val()).toEqual('0'); + +        scope.$apply(function() { +          scope.selected = scope.values[1]; +        }); + +        expect(element.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'); +          'ng:model': 'selected', +          'ng:options': 'item.name group by item.group for item in values' +        }); + +        scope.$apply(function() { +          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]; +        }); + +        expect(element.val()).toEqual('3'); -        var first = jqLite(select.find('optgroup')[0]); +        var first = jqLite(element.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 second = jqLite(element.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'); +        scope.$apply(function() { +          scope.selected = scope.values[0]; +        }); + +        expect(element.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'); +        createSelect({ +          'ng:model': 'selected', +          'ng:options': 'item.id as item.name for item in values' +        }); -        scope.selected = scope.values[1].id; -        scope.$digest(); -        expect(select.val()).toEqual('1'); +        scope.$apply(function() { +          scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}]; +          scope.selected = scope.values[0].id; +        }); + +        expect(element.val()).toEqual('0'); + +        scope.$apply(function() { +          scope.selected = scope.values[1].id; +        }); + +        expect(element.val()).toEqual('1');        }); +        it('should bind to object key', function() {          createSelect({ -          'ng:model':'selected', -          'ng:options':'key as value for (key, value) in object' +          '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'); +        scope.$apply(function() { +          scope.object = {red: 'FF0000', green: '00FF00', blue: '0000FF'}; +          scope.selected = 'green'; +        }); + +        expect(element.val()).toEqual('green'); + +        scope.$apply(function() { +          scope.selected = 'blue'; +        }); + +        expect(element.val()).toEqual('blue');        }); +        it('should bind to object value', function() {          createSelect({ -          'ng:model':'selected', -          'ng:options':'value as key for (key, value) in object' +          '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'); +        scope.$apply(function() { +          scope.object = {red: 'FF0000', green: '00FF00', blue:'0000FF'}; +          scope.selected = '00FF00'; +        }); + +        expect(element.val()).toEqual('green'); + +        scope.$apply(function() { +          scope.selected = '0000FF'; +        }); + +        expect(element.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); +        scope.$apply(function() { +          scope.values = [{name: 'A'}]; +          scope.selected = null; +        }); + +        expect(element.find('option').length).toEqual(2); +        expect(element.val()).toEqual(''); +        expect(jqLite(element.find('option')[0]).val()).toEqual(''); + +        scope.$apply(function() { +          scope.selected = scope.values[0]; +        }); + +        expect(element.val()).toEqual('0'); +        expect(element.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); +        scope.$apply(function() { +          scope.values = [{name: 'A'}]; +          scope.selected = null; +        }); + +        expect(element.find('option').length).toEqual(2); +        expect(element.val()).toEqual(''); +        expect(jqLite(element.find('option')[0]).val()).toEqual(''); + +        scope.$apply(function() { +          scope.selected = scope.values[0]; +        }); + +        expect(element.val()).toEqual('0'); +        expect(element.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); +        scope.$apply(function() { +          scope.values = [{name: 'A'}]; +          scope.selected = {}; +        }); + +        expect(element.find('option').length).toEqual(2); +        expect(element.val()).toEqual('?'); +        expect(jqLite(element.find('option')[0]).val()).toEqual('?'); + +        scope.$apply(function() { +          scope.selected = scope.values[0]; +        }); + +        expect(element.val()).toEqual('0'); +        expect(element.find('option').length).toEqual(1);        }); +        it('should select correct input if previously selected option was "?"', function() {          createSingleSelect(); -        scope.values = [{name:'A'},{name:'B'}]; -        scope.selected = {}; -        scope.$digest(); -        expect(select.find('option').length).toEqual(3); -        expect(select.val()).toEqual('?'); -        expect(select.find('option').eq(0).val()).toEqual('?'); -        browserTrigger(select.find('option').eq(1)); -        expect(select.val()).toEqual('0'); -        expect(select.find('option').eq(0).prop('selected')).toBeTruthy(); -        expect(select.find('option').length).toEqual(2); +        scope.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = {}; +        }); + +        expect(element.find('option').length).toEqual(3); +        expect(element.val()).toEqual('?'); +        expect(element.find('option').eq(0).val()).toEqual('?'); + +        browserTrigger(element.find('option').eq(1)); +        expect(element.val()).toEqual('0'); +        expect(element.find('option').eq(0).prop('selected')).toBeTruthy(); +        expect(element.find('option').length).toEqual(2);        });      });      describe('blank option', function () { +        it('should be compiled as template, be watched and updated', function () {          var option; -          createSingleSelect('<option value="">blank is {{blankVal}}</option>'); -        scope.blankVal = 'so blank'; -        scope.values = [{name:'A'}]; -        scope.$digest(); + +        scope.$apply(function() { +          scope.blankVal = 'so blank'; +          scope.values = [{name: 'A'}]; +        });          // check blank option is first and is compiled -        expect(select.find('option').length == 2); -        option = jqLite(select.find('option')[0]); +        expect(element.find('option').length).toBe(2); +        option = element.find('option').eq(0);          expect(option.val()).toBe('');          expect(option.text()).toBe('blank is so blank'); -        // change blankVal and $digest -        scope.blankVal = 'not so blank'; -        scope.$digest(); +        scope.$apply(function() { +          scope.blankVal = 'not so blank'; +        });          // check blank option is first and is compiled -        expect(select.find('option').length == 2); -        option = jqLite(select.find('option')[0]); +        expect(element.find('option').length).toBe(2); +        option = element.find('option').eq(0);          expect(option.val()).toBe('');          expect(option.text()).toBe('blank is not so blank');        }); +        it('should support binding via ng:bind-template attribute', function () {          var option; -          createSingleSelect('<option value="" ng:bind-template="blank is {{blankVal}}"></option>'); -        scope.blankVal = 'so blank'; -        scope.values = [{name:'A'}]; -        scope.$digest(); + +        scope.$apply(function() { +          scope.blankVal = 'so blank'; +          scope.values = [{name: 'A'}]; +        });          // check blank option is first and is compiled -        expect(select.find('option').length == 2); -        option = jqLite(select.find('option')[0]); +        expect(element.find('option').length).toBe(2); +        option = element.find('option').eq(0);          expect(option.val()).toBe('');          expect(option.text()).toBe('blank is so blank');        }); +        it('should support biding via ng:bind attribute', function () {          var option; -          createSingleSelect('<option value="" ng:bind="blankVal"></option>'); -        scope.blankVal = 'is blank'; -        scope.values = [{name:'A'}]; -        scope.$digest(); + +        scope.$apply(function() { +          scope.blankVal = 'is blank'; +          scope.values = [{name: 'A'}]; +        });          // check blank option is first and is compiled -        expect(select.find('option').length == 2); -        option = jqLite(select.find('option')[0]); +        expect(element.find('option').length).toBe(2); +        option = element.find('option').eq(0);          expect(option.val()).toBe('');          expect(option.text()).toBe('is blank');        }); +        it('should be rendered with the attributes preserved', function () {          var option; -          createSingleSelect('<option value="" class="coyote" id="road-runner" ' +            'custom-attr="custom-attr">{{blankVal}}</option>'); -        scope.blankVal = 'is blank'; -        scope.$digest(); + +        scope.$apply(function() { +          scope.blankVal = 'is blank'; +        });          // check blank option is first and is compiled -        option = jqLite(select.find('option')[0]); +        option = element.find('option').eq(0);          expect(option.hasClass('coyote')).toBeTruthy();          expect(option.attr('id')).toBe('road-runner');          expect(option.attr('custom-attr')).toBe('custom-attr'); @@ -519,76 +645,101 @@ describe('select', function() {      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'); +        scope.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = scope.values[0]; +        }); + +        expect(element.val()).toEqual('0'); + +        element.val('1'); +        browserTrigger(element, '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'); +        createSelect({ +          'ng:model': 'selected', +          'ng:options': 'item.id as item.name for item in values' +        }); -        select.val('1'); -        browserTrigger(select, 'change'); +        scope.$apply(function() { +          scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}]; +          scope.selected = scope.values[0].id; +        }); + +        expect(element.val()).toEqual('0'); + +        element.val('1'); +        browserTrigger(element, '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'); +        scope.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = scope.values[0]; +          element.val('0'); +        }); + +        element.val(''); +        browserTrigger(element, '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]).prop('selected')).toBeFalsy(); -        expect(jqLite(select.find('option')[1]).prop('selected')).toBeFalsy(); +        scope.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = []; +        }); -        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); +        expect(element.find('option').length).toEqual(2); +        expect(element.find('option')[0].selected).toBeFalsy(); +        expect(element.find('option')[1].selected).toBeFalsy(); -        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); +        scope.$apply(function() { +          scope.selected.push(scope.values[1]); +        }); + +        expect(element.find('option').length).toEqual(2); +        expect(element.find('option')[0].selected).toBeFalsy(); +        expect(element.find('option')[1].selected).toBeTruthy(); + +        scope.$apply(function() { +          scope.selected.push(scope.values[0]); +        }); + +        expect(element.find('option').length).toEqual(2); +        expect(element.find('option')[0].selected).toBeTruthy(); +        expect(element.find('option')[1].selected).toBeTruthy();        }); +        it('should update model on change', function() {          createMultiSelect(); -        scope.values = [{name:'A'}, {name:'B'}]; -        scope.selected = []; -        scope.$digest(); -        select.find('option')[0].selected = true; +        scope.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = []; +        }); -        browserTrigger(select, 'change'); +        element.find('option')[0].selected = true; + +        browserTrigger(element, 'change');          expect(scope.selected).toEqual([scope.values[0]]);        }); @@ -602,16 +753,57 @@ describe('select', function() {          scope.selected = ['1'];          scope.$digest(); -        expect(select.find('option')[1].selected).toBe(true); +        expect(element.find('option')[1].selected).toBe(true); -        select.find('option')[0].selected = true; -        browserTrigger(select, 'change'); +        element.find('option')[0].selected = true; +        browserTrigger(element, 'change');          expect(scope.selected).toEqual(['0', '1']); -        select.find('option')[1].selected = false; -        browserTrigger(select, 'change'); +        element.find('option')[1].selected = false; +        browserTrigger(element, 'change');          expect(scope.selected).toEqual(['0']);        });      }); + + +    describe('ng:required', function() { + +      it('should allow bindings on ng:required', function() { +        createSelect({ +          'ng:model': 'value', +          'ng:options': 'item.name for item in values', +          'ng:required': '{{required}}' +        }, true); + + +        scope.$apply(function() { +          scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}]; +          scope.required = false; +        }); + +        element.val(''); +        browserTrigger(element, 'change'); +        expect(element).toBeValid(); + +        scope.$apply(function() { +          scope.required = true; +        }); +        expect(element).toBeInvalid(); + +        scope.$apply(function() { +          scope.value = scope.values[0]; +        }); +        expect(element).toBeValid(); + +        element.val(''); +        browserTrigger(element, 'change'); +        expect(element).toBeInvalid(); + +        scope.$apply(function() { +          scope.required = false; +        }); +        expect(element).toBeValid(); +      }); +    });    });  }); | 
