UNPKG

angular-ivh-treeview

Version:

Treeview for angular with filtering and checkboxes

370 lines (307 loc) 13 kB
/*global jasmine, jQuery, describe, beforeEach, afterEach, it, module, inject, expect */ describe('Directive ivhTreeview', function() { 'use strict'; var ng = angular , $ = jQuery; var scope, compile, exception; beforeEach(module('ivh.treeview')); beforeEach(function() { exception = undefined; }); var tplBasic = '<div ivh-treeview="bag1"></div>'; var tplValidate = '<div ivh-treeview="bag1" ivh-treeview-validate="true"></div>'; var tplExpand = '<div ivh-treeview="bag1" ivh-treeview-expand-to-depth="1"></div>'; var tplExpandedAttr = '<div ivh-treeview="bag1" ivh-treeview-expanded-attribute="\'expanded\'"></div>'; var tplObjRoot = '<div ivh-treeview="bag1[0]"></div>'; var tplOptions = '<div ivh-treeview="bag1" ivh-treeview-options="customOpts"></div>'; var tplInlineTpls = '<div ivh-treeview="bag1" ivh-treeview-twistie-collapsed-tpl="\'[BOOM]\'"></div>'; var tplFilter = [ '<div', 'ivh-treeview="bag1"', 'ivh-treeview-filter="myFilter"', '></div>' ].join('\n'); var tplToggleHandler = [ '<div', 'ivh-treeview="bag1"', 'ivh-treeview-on-toggle="onNodeToggle(ivhNode, ivhIsExpanded, ivhTree)"', '></div>' ].join('\n'); var tplCbClickHandler = [ '<div', 'ivh-treeview="bag1"', 'ivh-treeview-on-cb-change="onCbChange(ivhNode, ivhIsSelected, ivhTree)"', '></div>' ].join('\n'); beforeEach(inject(function($rootScope, $compile) { scope = $rootScope.$new(); scope.bag1 = [{ label: 'top hat', children: [{ label: 'flat cap' }, { label: 'fedora', children: [ {label: 'gatsby'}, {label: 'gatsby 2'} ] }] }, { label: 'baseball', children: [] }]; compile = function(tpl, scp) { var $el = $compile(ng.element(tpl))(scp); scp.$apply(); return $el; }; })); afterEach(inject(function($timeout) { $timeout.verifyNoPendingTasks(); })); describe('basics', function() { var $el; it('should create a tree layout', function() { $el = compile(tplBasic, scope); expect($el.find('ul ul ul li').length).toBe(2); }); it('should add label titles to tree nodes', function() { $el = compile(tplBasic, scope); expect($el.find('[title="fedora"]').length).toBe(1); }); /** * @todo Collapsing/Expanding */ it('should allow expansion by default to a given depth', function() { $el = compile(tplExpand, scope); expect($el.find('[title="top hat"]').parent('li').hasClass('ivh-treeview-node-collapsed')).toBe(false); expect($el.find('[title="fedora"]').parent('li').hasClass('ivh-treeview-node-collapsed')).toBe(true); }); it('should honor inline expanded attribute declarations', function() { $el = compile(tplExpandedAttr, scope); var fedora = scope.bag1[0].children[1] , $fedora = $el.find('[title="fedora"]'); $fedora.find('[ivh-treeview-twistie]').click(); expect($fedora.parent().hasClass('ivh-treeview-node-collapsed')).toBe(false); expect(fedora.__ivhTreeviewExpanded).toBeUndefined(); expect(fedora.expanded).toBe(true); }); it('should allow roots objects', function() { $el = compile(tplObjRoot, scope); expect($el.find('ul').first().find('ul').length).toBe(2); }); it('should update indeterminate statuses', function() { $el = compile(tplBasic, scope); $el.find('[title="fedora"] input').first().click(); scope.$apply(); expect(scope.bag1[0].__ivhTreeviewIndeterminate).toBe(true); expect($el.find('input').first().prop('indeterminate')).toBe(true); // I've noticed that deselecting a child can leave ancestors up to root // unchecked and not-indeterminate when they should be. $el.find('[title="gatsby"] input').first().click(); scope.$apply(); expect($el.find('[title="fedora"] input').first().prop('indeterminate')).toBe(true); expect(scope.bag1[0].children[1].__ivhTreeviewIndeterminate).toBe(true); expect(scope.bag1[0].children[1].selected).toBe(false); }); it('should optionally validate the tree on creation', function() { scope.bag1[0].children[1].children[0].selected = false; $el = compile(tplValidate, scope); expect($el.find('[title="top hat"]').find('input').first().prop('indeterminate')).toBe(true); }); it('should update when child nodes are added (push)', function() { $el = compile(tplBasic, scope); scope.bag1[1].children.push({label: 'five panel baseball'}); scope.$apply(); expect($el.find('[title="five panel baseball"]').length).toBe(1); expect($el.find('[title="baseball"]').parent().hasClass('ivh-treeview-node-leaf')).toBe(false); }); it('should update when child nodes are added (re-assignment)', function() { $el = compile(tplBasic, scope); scope.bag1[1].children = [{label: 'five panel baseball'}]; scope.$apply(); expect($el.find('[title="five panel baseball"]').length).toBe(1); expect($el.find('[title="baseball"]').parent().hasClass('ivh-treeview-node-leaf')).toBe(false); }); it('should allow an options object for overrides', function() { scope.customOpts = { useCheckboxes: false, twistieCollapsedTpl: '[BOOM]' }; $el = compile(tplOptions, scope); expect($el.find('input[type="checkbox"]').length).toBe(0); expect($el.find('.ivh-treeview-twistie-collapsed').eq(0).text().trim()).toBe('[BOOM]'); }); it('should allow attribute level twistie templates', function() { $el = compile(tplInlineTpls, scope); expect($el.find('.ivh-treeview-twistie-collapsed').eq(0).text().trim()).toBe('[BOOM]'); }); }); describe('filtering', function() { var $el; beforeEach(function() { $el = compile(tplFilter, scope); scope.myFilter = 'baseball'; scope.$apply(); }); it('should hide filtered out nodes', function() { expect($el.find('[title="top hat"]').is(':visible')).toBe(false); /** * @todo Why does this fail? * Elements are not in DOM */ //expect($el.find('[title="baseball"]').is(':visible')).toBe(true); }); describe('object filtering', function() { beforeEach(function() { $el = compile(tplFilter, scope); scope.myFilter = {label: 'fedora'}; scope.$apply(); }); it('should hide filtered out nodes', function() { expect($el.find('[title="baseball"]').closest('.ng-hide').length > 0).toBe(true); }); it('should show parent nodes', function() { expect($el.find('[title="top hat"]').closest('.ng-hide').length > 0).toBe(false); }); it('should show filtered nodes', function() { expect($el.find('[title="fedora"]').closest('.ng-hide').length > 0).toBe(false); }); it('should hide filtered out child nodes', function() { expect($el.find('[title="gatsby"]').closest('.ng-hide').length > 0).toBe(true); }); }); describe('function filtering', function() { beforeEach(function() { $el = compile(tplFilter, scope); scope.myFilter = function (item) { return item.label === 'fedora'; }; scope.$apply(); }); it('should hide filtered out nodes', function() { expect($el.find('[title="baseball"]').closest('.ng-hide').length > 0).toBe(true); }); it('should show parent nodes', function() { expect($el.find('[title="top hat"]').closest('.ng-hide').length > 0).toBe(false); }); it('should show filtered nodes', function() { expect($el.find('[title="fedora"]').closest('.ng-hide').length > 0).toBe(false); }); it('should hide filtered out child nodes', function() { expect($el.find('[title="gatsby"]').closest('.ng-hide').length > 0).toBe(true); }); }); }); describe('toggle handlers', function() { var $el, handlerSpy; beforeEach(function() { handlerSpy = jasmine.createSpy('handlerSpy'); scope.onNodeToggle = handlerSpy; $el = compile(tplToggleHandler, scope); }); it('should call the toggle handler once per click', function() { $el.find('[title="top hat"] [ivh-treeview-toggle]').first().click(); scope.$apply(); expect(handlerSpy.calls.count()).toEqual(1); }); it('should not call the toggle handler when a leaf is clicked', function() { $el.find('[title="gatsby"] [ivh-treeview-toggle]').first().click(); scope.$apply(); expect(handlerSpy.calls.count()).toEqual(0); }); it('should pass the clicked node to the handler', function() { $el.find('[title="top hat"] [ivh-treeview-toggle]').first().click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[0]).toBe(scope.bag1[0]); }); it('should pass the expanded state to the change handler', function() { $el.find('[title="top hat"] [ivh-treeview-toggle]').first().click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[1]).toBe(true); }); it('should pass the tree itself to the toggle handler', function() { $el.find('[title="top hat"] [ivh-treeview-toggle]').click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[2]).toBe(scope.bag1); }); it('should not generate an error when there is no handler', function() { delete scope.onNodeToggle; var exception; $el = compile(tplToggleHandler, scope); try { $el.find('[title="top hat"] [ivh-treeview-toggle]').click(); } catch(_exception) { exception = _exception; } expect(exception).toBeUndefined(); }); it('should pass the clicked node and tree to the callback via an object when registered through an options hash', function() { scope.opts = { onToggle: handlerSpy }; var tpl = '<div ivh-treeview="bag1" ivh-treeview-options="opts" ></div>'; $el = compile(tpl, scope); $el.find('[title="top hat"] [ivh-treeview-toggle]').first().click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[0]).toEqual({ ivhNode: scope.bag1[0], ivhIsExpanded: true, ivhTree: scope.bag1 }); }); }); describe('checkbox click handlers', function() { var $el, handlerSpy; beforeEach(function() { handlerSpy = jasmine.createSpy('handlerSpy'); scope.onCbChange = handlerSpy; $el = compile(tplCbClickHandler, scope); }); it('should call the change handler when checkbox state is changed', function() { $el.find('[title="top hat"] [type=checkbox]').first().click(); scope.$apply(); expect(handlerSpy.calls.count()).toEqual(1); }); it('should pass the selected node to the handler', function() { $el.find('[title="top hat"] [type=checkbox]').first().click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[0]).toBe(scope.bag1[0]); }); it('should pass the checkbox state to the change handler', function() { var $cb = $el.find('[title="top hat"] [type=checkbox]').first(); $cb.click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[1]).toBe($cb.prop('checked')); }); it('should pass the tree itself to the change handler', function() { $el.find('[title="top hat"] [type=checkbox]').first().click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[2]).toBe(scope.bag1); }); it('should not generate an error when there is no handler', function() { delete scope.onCbChange; var exception; $el = compile(tplCbClickHandler, scope); try { $el.find('[title="top hat"] [type=checkbox]').click(); } catch(_exception) { exception = _exception; } expect(exception).toBeUndefined(); }); it('should pass the clicked node, selected state, and tree to the callback via an object when registered through an options hash', function() { scope.opts = { onCbChange: handlerSpy }; var tpl = '<div ivh-treeview="bag1" ivh-treeview-options="opts" ></div>'; $el = compile(tpl, scope); $el.find('[title="top hat"] [type=checkbox]').first().click(); scope.$apply(); expect(handlerSpy.calls.mostRecent().args[0]).toEqual({ ivhNode: scope.bag1[0], ivhIsSelected: jasmine.any(Boolean), ivhTree: scope.bag1 }); }); }); });