diff options
| author | Misko Hevery | 2012-03-23 14:03:24 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2012-03-28 11:16:35 -0700 | 
| commit | 2430f52bb97fa9d682e5f028c977c5bf94c5ec38 (patch) | |
| tree | e7529b741d70199f36d52090b430510bad07f233 /test/ng/directive/selectSpec.js | |
| parent | 944098a4e0f753f06b40c73ca3e79991cec6c2e2 (diff) | |
| download | angular.js-2430f52bb97fa9d682e5f028c977c5bf94c5ec38.tar.bz2 | |
chore(module): move files around in preparation for more modules
Diffstat (limited to 'test/ng/directive/selectSpec.js')
| -rw-r--r-- | test/ng/directive/selectSpec.js | 863 | 
1 files changed, 863 insertions, 0 deletions
| diff --git a/test/ng/directive/selectSpec.js b/test/ng/directive/selectSpec.js new file mode 100644 index 00000000..2e3cfaaf --- /dev/null +++ b/test/ng/directive/selectSpec.js @@ -0,0 +1,863 @@ +'use strict'; + +describe('select', function() { +  var scope, formElement, element, $compile; + +  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; +    $compile = $injector.get('$compile'); +    formElement = element = null; +  })); + +  afterEach(function() { +    dealoc(formElement); +  }); + + +  describe('select-one', function() { + +    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.$apply(function() { +        scope.a = 'foo'; +        scope.b = 'bar'; +      }); + +      expect(element.text()).toBe('foobarC'); +    }); + + +    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.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.$apply(function() { +        scope.selection = ''; +      }); + +      expect(scope.form.select.$error.required).toBeTruthy(); +      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.$apply(function() { +        scope.selection = ['A']; +      }); + +      expect(element.find('option')[0].selected).toEqual(true); +      expect(element.find('option')[1].selected).toEqual(false); + +      scope.$apply(function() { +        scope.selection.push('B'); +      }); + +      expect(element.find('option')[0].selected).toEqual(true); +      expect(element.find('option')[1].selected).toEqual(true); +    }); + + +    it('should require', function() { +      compile( +        '<select name="select" ng-model="selection" multiple required>' + +          '<option>A</option>' + +          '<option>B</option>' + +        '</select>'); + +      scope.$apply(function() { +        scope.selection = []; +      }); + +      expect(scope.form.select.$error.required).toBeTruthy(); +      expect(element).toBeInvalid(); +      expect(element).toBePristine(); + +      scope.$apply(function() { +        scope.selection = ['A']; +      }); + +      expect(element).toBeValid(); +      expect(element).toBePristine(); + +      element[0].value = 'B'; +      browserTrigger(element, 'change'); +      expect(element).toBeValid(); +      expect(element).toBeDirty(); +    }); +  }); + + +  describe('ng-options', function() { +    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 ? (isString(blank) ? blank : '<option value="">blank</option>') : '') + +        (unknown ? (isString(unknown) ? unknown : '<option value="?">unknown</option>') : '') + +      '</select>'; + +      compile(html); +    } + +    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); +    } + + +    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.$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-options': 'value as key for (key, value) in object' +      }); + +      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.$apply(function() { +        scope.object.azur = '8888FF'; +      }); + +      options = element.find('option'); +      expect(options[3].selected).toEqual(true); +    }); + + +    it('should grow list', function() { +      createSingleSelect(); + +      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.$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.$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.$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.$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.$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.$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(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(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.$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.$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' +        }); + +        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' +        }); + +        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.$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.$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.$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.$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.$apply(function() { +          scope.blankVal = 'so blank'; +          scope.values = [{name: 'A'}]; +        }); + +        // check blank option is first and is compiled +        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'); + +        scope.$apply(function() { +          scope.blankVal = 'not so blank'; +        }); + +        // check blank option is first and is compiled +        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.$apply(function() { +          scope.blankVal = 'so blank'; +          scope.values = [{name: 'A'}]; +        }); + +        // check blank option is first and is compiled +        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.$apply(function() { +          scope.blankVal = 'is blank'; +          scope.values = [{name: 'A'}]; +        }); + +        // check blank option is first and is compiled +        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.$apply(function() { +          scope.blankVal = 'is blank'; +        }); + +        // check blank option is first and is compiled +        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'); +      }); +    }); + + +    describe('on change', function() { + +      it('should update model on change', function() { +        createSingleSelect(); + +        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.$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.$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.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = []; +        }); + +        expect(element.find('option').length).toEqual(2); +        expect(element.find('option')[0].selected).toBeFalsy(); +        expect(element.find('option')[1].selected).toBeFalsy(); + +        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.$apply(function() { +          scope.values = [{name: 'A'}, {name: 'B'}]; +          scope.selected = []; +        }); + +        element.find('option')[0].selected = true; + +        browserTrigger(element, 'change'); +        expect(scope.selected).toEqual([scope.values[0]]); +      }); + +      it('should select from object', function() { +        createSelect({ +          'ng-model':'selected', +          'multiple':true, +          'ng-options':'key as value for (key,value) in values' +        }); +        scope.values = {'0':'A', '1':'B'}; + +        scope.selected = ['1']; +        scope.$digest(); +        expect(element.find('option')[1].selected).toBe(true); + +        element.find('option')[0].selected = true; +        browserTrigger(element, 'change'); +        expect(scope.selected).toEqual(['0', '1']); + +        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(); +      }); +    }); +  }); + + +  describe('OPTION value', function() { +    beforeEach(function() { +      this.addMatchers({ +        toHaveValue: function(expected){ +          this.message = function() { +            return 'Expected "' + this.actual.html() + '" to have value="' + expected + '".'; +          }; + +          var value; +          htmlParser(this.actual.html(), { +            start:function(tag, attrs){ +              value = attrs.value; +            }, +            end:noop, +            chars:noop +          }); +          return trim(value) == trim(expected); +        } +      }); +    }); + + +    it('should populate value attribute on OPTION', inject(function($rootScope, $compile) { +      element = $compile('<select ng-model="x"><option>abc</option></select>')($rootScope) +      expect(element).toHaveValue('abc'); +    })); + +    it('should ignore value if already exists', inject(function($rootScope, $compile) { +      element = $compile('<select ng-model="x"><option value="abc">xyz</option></select>')($rootScope) +      expect(element).toHaveValue('abc'); +    })); + +    it('should set value even if newlines present', inject(function($rootScope, $compile) { +      element = $compile('<select ng-model="x"><option attr="\ntext\n" \n>\nabc\n</option></select>')($rootScope) +      expect(element).toHaveValue('\nabc\n'); +    })); + +    it('should set value even if self closing HTML', inject(function($rootScope, $compile) { +      // IE removes the \n from option, which makes this test pointless +      if (msie) return; +      element = $compile('<select ng-model="x"><option>\n</option></select>')($rootScope) +      expect(element).toHaveValue('\n'); +    })); +  }); +}); | 
