'use strict'; describe('select', function() { var compile = null, element = null, scope = null; beforeEach(inject(function($compile, $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; }; })); 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(''); scope.a = 'foo'; scope.b = 'bar'; scope.$digest(); expect(element.text()).toBe('foobarC'); }); it('should require', inject(function($formFactory) { compile(''); 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(''); expect(element).toBeValid(); expect(element).toBePristine(); }); }); describe('select-multiple', function() { it('should support type="select-multiple"', function() { compile(''); scope.selection = ['A']; scope.$digest(); expect(element[0].childNodes[0].selected).toEqual(true); }); it('should require', inject(function($formFactory) { compile(''); 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 = 'blank') : '') + (unknown ? (isString(unknown) ? unknown : '') : '') + ''; 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 ?"', inject(function($rootScope, $exceptionHandler) { expect(function() { compile(''); }).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(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); }); 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(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); 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(''); 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(''); scope.values.push({name:'B'}); scope.$digest(); expect(select.find('option').length).toEqual(2); expect(sortedHtml(select.find('option')[0])).toEqual(''); expect(sortedHtml(select.find('option')[1])).toEqual(''); }); 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(''); expect(sortedHtml(select.find('option')[1])).toEqual(''); scope.values.pop(); scope.$digest(); expect(select.find('option').length).toEqual(1); expect(sortedHtml(select.find('option')[0])).toEqual(''); 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(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); }); 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); }); 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); }); }); describe('blank option', function () { it('should be compiled as template, be watched and updated', function () { var option; createSingleSelect(''); scope.blankVal = 'so blank'; scope.values = [{name:'A'}]; scope.$digest(); // check blank option is first and is compiled expect(select.find('option').length == 2); option = jqLite(select.find('option')[0]); expect(option.val()).toBe(''); expect(option.text()).toBe('blank is so blank'); // change blankVal and $digest scope.blankVal = 'not so blank'; scope.$digest(); // check blank option is first and is compiled expect(select.find('option').length == 2); option = jqLite(select.find('option')[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(''); scope.blankVal = 'so blank'; scope.values = [{name:'A'}]; scope.$digest(); // check blank option is first and is compiled expect(select.find('option').length == 2); option = jqLite(select.find('option')[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(''); scope.blankVal = 'is blank'; scope.values = [{name:'A'}]; scope.$digest(); // check blank option is first and is compiled expect(select.find('option').length == 2); option = jqLite(select.find('option')[0]); expect(option.val()).toBe(''); expect(option.text()).toBe('is blank'); }); it('should be rendered with the attributes preserved', function () { var option; createSingleSelect(''); scope.blankVal = 'is blank'; scope.$digest(); // check blank option is first and is compiled option = jqLite(select.find('option')[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.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]).prop('selected')).toBeFalsy(); expect(jqLite(select.find('option')[1]).prop('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]]); }); }); }); });