diff --git a/.gitignore b/.gitignore index 5e22086..bebc304 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ -.idea +.* node_modules bower_components coverage treecontrol.iml .DS_Store Chromium 49.0.2623 (Ubuntu) + diff --git a/Gruntfile.js b/Gruntfile.js index 4851758..7f17e8a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -34,6 +34,7 @@ module.exports = function(grunt) { 'demo/angular.1.3.12.js', 'demo/angular-mocks.1.3.12.js', 'angular-tree-control.js', + 'context-menu.js', 'test/**/*.js' ] } diff --git a/README.md b/README.md index 0ea4e53..ce390a0 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,13 @@ Copy the script and css into your project and add a script and link tag to your ```html <script type="text/javascript" src="/angular-tree-control.js"></script> + +<!-- Include context-menu module if you're going to use menu-id attribute --> +<script type="text/javascript" src="/context-menu.js"></script> + <!-- link for CSS when using the tree as a Dom element --> <link rel="stylesheet" type="text/css" href="css/tree-control.css"> + <!-- link for CSS when using the tree as an attribute --> <link rel="stylesheet" type="text/css" href="css/tree-control-attribute.css"> ``` @@ -113,6 +118,7 @@ Attributes of angular treecontrol - `expanded-nodes` : [Array[Node]] binding for the expanded nodes in the tree. Updating this value updates the nodes that are expanded in the tree. - `on-selection` : `(node, selected)` callback called whenever selecting a node in the tree. The callback expression can use the selected node (`node`) and a boolean which indicates if the node was selected or deselected (`selected`). - `on-node-toggle` : `(node, expanded)` callback called whenever a node expands or collapses in the tree. The callback expression can use the toggled node (`node`) and a boolean which indicates expansion or collapse (`expanded`). +- `on-right-click` : `(node)` callback called whenever a node is right-clicked. - `options` : different options to customize the tree control. - `multiSelection` : [Boolean] enable multiple nodes selection in the tree. - `nodeChildren` : the name of the property of each node that holds the node children. Defaults to 'children'. @@ -133,6 +139,7 @@ Attributes of angular treecontrol - `reverse-order` : whether or not to reverse the ordering of sibling nodes based on the value of `order-by` - `filter-expression` : value for ng-repeat to use for filtering the sibling nodes - `filter-comparator` : value for ng-repeat to use for comparing nodes with the filter expression +- `menu-id` : the id of an ul element which will be displayed after a right-click ### The tree labels diff --git a/angular-tree-control.js b/angular-tree-control.js index 3043027..c0c3212 100644 --- a/angular-tree-control.js +++ b/angular-tree-control.js @@ -80,11 +80,12 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex ensureDefault($scope.options, "allowDeselect", true); ensureDefault($scope.options, "isSelectable", defaultIsSelectable); } - - angular.module( 'treeControl', [] ) + + angular.module( 'treeControl', ['contextMenu'] ) .constant('treeConfig', { templateUrl: null }) + .directive( 'treecontrol', ['$compile', function( $compile ) { /** * @param cssClass - the css class @@ -100,9 +101,6 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex else return ""; } - - - return { restrict: 'EA', require: "treecontrol", @@ -114,6 +112,8 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex expandedNodes: "=?", onSelection: "&", onNodeToggle: "&", + onRightClick: "&", + menuId: "@", options: "=?", orderBy: "=?", reverseOrder: "@", @@ -121,11 +121,11 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex filterComparator: "=?" }, controller: ['$scope', '$templateCache', '$interpolate', 'treeConfig', function ($scope, $templateCache, $interpolate, treeConfig) { - + $scope.options = $scope.options || {}; - + ensureAllDefaultOptions($scope); - + $scope.selectedNodes = $scope.selectedNodes || []; $scope.expandedNodes = $scope.expandedNodes || []; $scope.expandedNodesMap = {}; @@ -249,6 +249,25 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex } }; + $scope.rightClickNodeLabel = function( targetNode, $event ) { + + // Is there a right click function?? + if($scope.onRightClick) { + + // Turn off the browser default context-menu + if($event) + $event.preventDefault(); + + // Are are we changing the 'selected' node (as well)? + if ($scope.selectedNode != targetNode) { + this.selectNodeLabel(targetNode); + } + + // Finally go do what they asked + $scope.onRightClick({node: targetNode}); + } + }; + $scope.selectedClass = function() { var isThisNodeSelected = isSelectedNode(this.node); var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false); @@ -266,6 +285,9 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex }; //tree template + var rcLabel = $scope.onRightClick ? ' tree-right-click="rightClickNodeLabel(node)"' : ''; + var ctxMenuId = $scope.menuId ? ' context-menu-id="'+ $scope.menuId+'"' : ''; + $scope.isReverse = function() { return !($scope.reverseOrder === 'false' || $scope.reverseOrder === 'False' || $scope.reverseOrder === '' || $scope.reverseOrder === false); }; @@ -273,7 +295,6 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex $scope.orderByFunc = function() { return $scope.orderBy; }; -// return "" + $scope.orderBy; var templateOptions = { orderBy: $scope.orderBy ? " | orderBy:orderByFunc():isReverse()" : '', @@ -298,7 +319,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex 'set-node-to-data>' + '<i class="tree-branch-head" ng-class="iBranchClass()" ng-click="selectNodeHead(node)"></i>' + '<i class="tree-leaf-head {{options.iLeafClass}}"></i>' + - '<div class="tree-label {{options.labelClass}}" ng-class="[selectedClass(), unselectableClass()]" ng-click="selectNodeLabel(node)" tree-transclude></div>' + + '<div class="tree-label {{options.labelClass}}" ng-class="[selectedClass(), unselectableClass()]" ng-click="selectNodeLabel(node)" ' + rcLabel + ctxMenuId + ' tree-transclude></div>' + '<treeitem ng-if="nodeExpanded()"></treeitem>' + '</li>' + '</ul>'; @@ -306,6 +327,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex this.template = $compile($interpolate(template)({options: templateOptions})); }], + compile: function(element, attrs, childTranscludeFn) { return function ( scope, element, attrs, treemodelCntr ) { @@ -381,6 +403,18 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex } }; }]) + + .directive('treeRightClick', function($parse) { + return function(scope, element, attrs) { + var fn = $parse(attrs.treeRightClick); + element.bind('contextmenu', function(event) { + scope.$apply(function() { + fn(scope, {$event:event}); // go do our stuff + }); + }); + }; + }) + .directive("treeitem", function() { return { restrict: 'E', diff --git a/context-menu.js b/context-menu.js new file mode 100644 index 0000000..12b8ddb --- /dev/null +++ b/context-menu.js @@ -0,0 +1,173 @@ +(function ( angular ) { + 'use strict'; + + /* Figure out page (viewport) dimensions of current page, by + * putting an empty DIV in the bottom right, and checking its offset. + */ + function getPageDimensions() { + var bttmRight = document.createElement("div"); + bttmRight.setAttribute("style" , "visibility:hidden;position:fixed;bottom:0px;right:0px;"); + document.getElementsByTagName("body")[0].appendChild(bttmRight); + var pageWidth = bttmRight.offsetLeft; + var pageHeight = bttmRight.offsetTop; + bttmRight.parentNode.removeChild(bttmRight); + return { width:pageWidth, height:pageHeight }; + } + + angular.module( 'contextMenu', [] ) + + .directive('contextMenuId', ['$document', function($document) { + + return { + restrict : 'A', + scope : '@&', + compile: function compile(tElement, tAttrs, transclude) { + + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + var ul = angular.element(document.querySelector('#' + iAttrs.contextMenuId)); + + ul.css({ 'display' : 'none'}); + + // right-click on context-menu will show the menu + iElement.bind('contextmenu', function showContextMenu(event) { + + // don't do the normal browser right-click context menu + event.preventDefault(); + + // Organise to show off the menu (in roughly the right place) + ul.css({ + visibility:"hidden", + position: "fixed", + display: "block", + left: event.clientX + 'px', + top: event.clientY + 'px' + }); + + var ulDim = { height: ul.prop("clientHeight"), + width: ul.prop("cientWidth") + }; + + var pgDim = getPageDimensions(); + + // will ctxMenu fit on screen (height-wise) ? + // TODO: figure out why we need the fudge-factor of 14 + var ulTop = event.clientY + ulDim.height <= pgDim.height - 14 + ? event.clientY + : pgDim.height - ulDim.height - 14; + + // will ctxMenu fit on screen (width-wise) ? + var ulLeft = event.clientX + ulDim.width <= pgDim.width - 2 + ? event.clientX + : pgDim.width - ulDim.width - 2; + + // Ok, now show it off in the right place + ul.css({ + visibility:"visible", + position: "fixed", + display: "block", + left: ulLeft + 'px', + top: ulTop + 'px' + }); + + // setup a one-time click event on the document to hide the dropdown-menu + $document.one('click', function hideContextMenu(event) { + ul.css({ + 'display' : 'none' + }); + }); + }); + } + }; + } + }; + }]) + + .directive('contextSubmenuId', ['$document', function($document) { + return { + restrict : 'A', + scope : '@&', + compile: function compile(tElement, tAttrs, transclude) { + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + var ul = angular.element(document.querySelector('#' + iAttrs.contextSubmenuId)); + + ul.css({ 'display' : 'none'}); + + + iElement.bind('mouseover', function showSubContextMenu(event) { + // use CSS to move and show the sub dropdown-menu + if(ul.css("display") == 'none') { + + // Organise to show off the sub-menu (in roughly the right place) + ul.css({ + visibility:"hidden", + position: "fixed", + display: "block", + left: event.clientX + 'px', + top: event.clientY + 'px' + }); + + var ulDim = { height: ul.prop("clientHeight"), + width: ul.prop("clientWidth") + }; + + var pgDim = getPageDimensions(); + + + // Will ctxSubMenu fit (height-wise) ? + // TODO: figure out why we need the fudge-factor of 14 + var ulTop = event.clientY + ulDim.height <= pgDim.height - 14 + ? event.clientY + : pgDim.height - ulDim.height - 14; + + // Will ctxSubMenu fit (on the right of parent menu) ? + var ulLeft = + (event.target.offsetParent.offsetLeft + + event.target.clientWidth + ulDim.width < pgDim.width) + ? event.target.offsetParent.offsetLeft + + event.target.clientWidth + + : event.target.offsetParent.offsetLeft - ulDim.width; + + // OK, now show it off in the right place + ul.css({ + visibility:"visible", + position: "fixed", + display: "block", + left: ulLeft + 'px', + top: ulTop + 'px' + }); + + // Each uncle/aunt menu item needs a mouseover event to make the subContext menu disappear + angular.forEach(iElement[0].parentElement.parentElement.children, function(child, ndx) { + if(child !== iElement[0].parentElement) { + angular.element(child).one('mouseover', function(event) { + if(ul.css("display") == 'block') { + ul.css({ + 'display' : 'none' + }); + } + }); + } + }); + } + + // setup a one-time click event on the document to hide the sub dropdown-menu + $document.one('click', function hideContextMenu(event) { + if(ul.css("display") == 'block') { + ul.css({ + 'display' : 'none' + }); + } + }); + }); + } + }; + } + }; + }]); + +})( angular ); diff --git a/index.html b/index.html index e816839..0fc9fae 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@ <link href="demo/prettify-style.css" rel="stylesheet" type="text/css"> <script src="angular-tree-control.js"></script> + <script src="context-menu.js"></script> <link rel="stylesheet" type="text/css" href="css/tree-control.css"> <link rel="stylesheet" type="text/css" href="css/tree-control-attribute.css"> <style> @@ -33,6 +34,7 @@ .type-hint-boolean {background:rgb(18, 131, 39);} .type-hint-function {background: rgb(36, 53, 131);} .type-hint-number {background:rgb(189, 63, 66);} + .dropdown-menu li a.disabled { pointer-events: none; cursor: default; color:#bbb; } </style> </head> <body ng-app="example"> @@ -1049,7 +1051,7 @@ <h1>Custom Branch / Leaf <small>(options.isLeaf)</small></h1> <section id="sorting" ng-controller="Sorting"> <div class="page-header"> - <h1>Sorting tree nodes <small>(order-by, reserve-order)</small></h1> + <h1>Sorting tree nodes <small>(order-by, reverse-order)</small></h1> </div> <div class="row"> <div class="col-md-6 show-grid"> @@ -1068,7 +1070,8 @@ <h1>Sorting tree nodes <small>(order-by, reserve-order)</small></h1> </div> </div> <div class="col-md-6"> - <p>The <code>order-by</code> and <code>reverse-order</code> properties allows controlling the sorting of the tree nodes. + <p>The <code>order-by</code> and <code>reverse-order</code> properties allows + controlling the sorting of the tree nodes. The value of those attributes is used with the <code>ng-repeat</code> orderBy filter - see more details at the <a href="https://code.angularjs.org/1.2.0-rc.3/docs/api/ng.filter:orderBy">Angular JS orderBy</a> docs.</p> <p>The sorting is done for each branch individually (sorting does not change the structure of the tree itself).</p> @@ -1232,15 +1235,130 @@ <h1>Custom Equality <small>(options.equality)</small></h1> </script> </section> + +<section id="rightclick" ng-controller="RightClick"> + <div class="page-header"> + <h1>Right Click & Context Menu <small>(on-right-click & menu-id)</small></h1> + + </div> + <div class="row"> + <div class="col-md-6 show-grid"> + <div class="panel panel-default"> + <div class="panel-body"> + <div class="example-caption">EXAMPLE:</div> + + <div save-content="rightclick-html"> + <treecontrol class="tree-classic" + tree-model="treedata" + expanded-nodes="expandedNodes" + on-selection="doSelect(node)" + on-right-click="doRightClick(node)" + menu-id="ctxMenu"> + {{node.label}} + </treecontrol> + + + <ul id="ctxMenu" class="dropdown-menu"> + <li><a ng-click="doCook()" + ng-class="isLeaf() ? '' : 'disabled'">Cook {{selectedFood.name}}</a></li> + <li class="divider"></li> + <li><a context-submenu-id="ctxSubMenuOrange" + ng-class="selectedFood.color == 'Orange' ? '' : 'disabled'">Orange...</a></li> + <li><a context-submenu-id="ctxSubMenuGreen" + ng-class="selectedFood.color == 'Green' ? '' : 'disabled'">Green...</a></li> + </ul> + + + <ul id="ctxSubMenuOrange" class="dropdown-menu"> + <li><a ng-click="doAction('juice' + selectedFood.name)">Juice {{selectedFood.name}}</a></li> + <li><a ng-click="doAction('peel orange food')">Peel Orange Food</a></li> + </ul> + + + <ul id="ctxSubMenuGreen" class="dropdown-menu"> + <li><a ng-click="doAction('chop' + selectedFood.name)">Chop {{selectedFood.name}}</a></li> + <li><a ng-click="doAction('shred green food')">Shred Green Food</a></li> + </ul> + </div> + </div> + </div> + </div> + <div class="col-md-6"> + <p>The angular tree control provides right-click and context menu functionality: + <ul> + <li> + <code>on-right-click</code> + is evaluated as an angular expression (like <code>ng-click</code> value) when + a right-click event is detected. If context menu is enabled, then this will be + evaluated before the context menu is opened. + </li> + <li> + <code>menu-id</code> is the <strong>id</strong> for an <code><ul></code> + element which will be displayed after a right-click has been detected. + </li> + </ul> + </p> + <p>The last right-click was on the <code>{{lastRightClickNode}}</code> node </p> + <p>The last food to be cooked was <code>{{lastCookedFood}}</code></p> + <p>The last context menu action was <code>{{lastAction}}</code></p> + </div> + </div> + <div class="row"> + <tabset> + <tab heading="Markup" > + <pre class="code" apply-content="rightclick-html" highlight-lang="html"></pre> + </tab> + <tab heading="JavaScript"> + <pre class="code" apply-content="rightclick-js" highlight-lang="js"></pre> + </tab> + </tabset> + </div> + + <script save-content="rightclick-js"> + function RightClick($scope) { + $scope.treedata=[ + { "label" : "Vegetables", "children" : [ + { "label": "Vegetable:Carrot (Orange)", "name" : "Carrot", "color" : "Orange", "class" : "Vegetable", "children" : [] }, + { "label": "Vegetable:Cabbbage (Green)", "name" : "Cabbage", "color" : "Green", "class" : "Vegetable", "children" : [] }, + { "label": "Vegetable:Potato (Brown)", "name" : "Potato", "color" : "Brown", "class" : "Vegetable", "children" : [] } + ]}, + { "label" : "Fruit", "children" : [ + { "label": "Fruit:Apricot (Orange)", "name" : "Apricot", "color" : "Orange", "class" : "Fruit", "children" : [] }, + { "label": "Fruit:Apple (Red)", "name" : "Apple", "color" : "Red", "class" : "Fruit", "children" : [] }, + { "label": "Fruit:Grape (Green)", "name" : "Grape", "color" : "Green", "class" : "Fruit", "children" : [] } + ]} + ]; + $scope.expandedNodes=[$scope.treedata[0], $scope.treedata[1]]; + + $scope.selectedFood = null; + $scope.doSelect = function(node) { $scope.selectedFood = node;}; + + $scope.lastRightClickNode = null; + $scope.doRightClick = function(node) { $scope.lastRightClickNode = node.label; } + + $scope.lastCookedFood = null; + $scope.doCook = function() { $scope.lastCookedFood = $scope.selectedFood.name; }; + + $scope.lastAction = null; + $scope.doAction = function(cmd) { $scope.lastAction = cmd; }; + + $scope.isLeaf = function() { return $scope.selectedFood && $scope.selectedFood.children.length == 0;}; + } + </script> +</section> + + <section id="externalTemplate" ng-controller="ExternalTemplate"> <div class="page-header"> <h1>External Template <small>(options.templateUrl, treeConfig.templateUrl)</small></h1> + </div> <div class="row"> <div class="col-md-6 show-grid"> <div class="panel panel-default"> <div class="panel-body"> <div class="example-caption">EXAMPLE:</div> + <div save-content="external-template-html"> <treecontrol class="tree-classic" tree-model="treedata" @@ -1325,6 +1443,7 @@ <h1>External Template <small>(options.templateUrl, treeConfig.templateUrl)</smal </script> </section> + </div> <div class="col-md-3"> <ul nav class="nav docs-sidenav"> @@ -1427,4 +1546,4 @@ <h1>External Template <small>(options.templateUrl, treeConfig.templateUrl)</smal } </script> </body> -</html> \ No newline at end of file +</html> diff --git a/karma.conf.js b/karma.conf.js index 936c672..0b787ef 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -16,6 +16,7 @@ module.exports = function(config) { 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'angular-tree-control.js', + 'context-menu.js', 'test/**/*.js' ], diff --git a/test/angular-tree-control-test.js b/test/angular-tree-control-test.js index a4e2113..a18e07e 100644 --- a/test/angular-tree-control-test.js +++ b/test/angular-tree-control-test.js @@ -77,6 +77,24 @@ describe('treeControl', function() { }); + describe('rendering context menu', function () { + it('should not render context-menu-id attributes if menu-id is not provided', function() { + $rootScope.treedata = createSubTree(2, 2); + element = $compile('<treecontrol tree-model="treedata">{{node.label}}</treecontrol>')($rootScope); + $rootScope.$digest(); + expect(element.find('div.tree-label:eq(0)')[0].attributes['context-menu-id']).toBeFalsy(); + }); + + it('should render context-menu-id attributes if menu-id is provided', function() { + + $rootScope.treedata = createSubTree(2,2); + element = $compile('<treecontrol tree-model="treedata" menu-id="ctxMenuId">{{node.label}}</treecontrol>')($rootScope); + $rootScope.$digest(); + expect(element.find('div.tree-label:eq(0)')[0].attributes['context-menu-id'].value).toBe("ctxMenuId"); + }); + }); + + describe('customising using options.isLeaf', function () { it('should display first level parents as collapsed nodes, including the leaf', function () { $rootScope.treedata = createSubTree(2, 2); @@ -290,9 +308,9 @@ describe('treeControl', function() { $rootScope.$digest(); element.find('li:eq(0) div').click(); - expect($rootScope.selectedItem).toBeUndefined() + expect($rootScope.selectedItem).toBeUndefined(); }); - + it('should not un-select a node after second click when allowDeselect==false', function () { $rootScope.treeOptions = {allowDeselect: false}; $rootScope.treedata = createSubTree(2, 2); @@ -322,6 +340,18 @@ describe('treeControl', function() { }); }); + describe('rightclick', function() { + it('should invoke right-click callback when item is right-clicked', function() { + + $rootScope.treedata = createSubTree(2,2); + element = $compile('<treecontrol tree-model="treedata" on-right-click="rightclick(node.label)">{{node.label}}</treecontrol>')($rootScope); + $rootScope.$digest(); + $rootScope.rightclick = jasmine.createSpy('rightclick'); + element.find('li:eq(1) div').triggerHandler('contextmenu'); + expect($rootScope.rightclick).toHaveBeenCalledWith($rootScope.treedata[1].label); + }); + }); + describe('toggle', function() { it('should call on-node-toggle when node head is clicked with the expanding node and expanding indication', function () { $rootScope.treedata = createSubTree(2, 2); @@ -584,24 +614,28 @@ describe('treeControl', function() { expect(element.find('li:eq(2)').text()).toBe('a'); }); - it('should support re-ordering as order-by is updated', function() { + it('should order sub-trees on different orders by expression', function() { $rootScope.treedata = [ - { label: "a", id: 2, children: [] }, - { label: "c", id: 1, children: [] }, - { label: "b", id: 3, children: [] } + { subTreeSortOrder:"-label", label: "a", children: [ {label:"a2", children:[]}, {label:"a3", children:[]}, {label:"a1", children:[]} ] }, + { subTreeSortOrder:"label", label: "b", children: [ {label:"b2", children:[]}, {label:"b3", children:[]}, {label:"b1", children:[]} ] } ]; - $rootScope.predicate = 'label'; - element = $compile('<treecontrol tree-model="treedata" order-by="predicate" reverse-order="{{reverse}}">{{node.label}}</treecontrol>')($rootScope); - $rootScope.$digest(); - expect(element.find('li:eq(0)').text()).toBe('a'); - expect(element.find('li:eq(1)').text()).toBe('b'); - expect(element.find('li:eq(2)').text()).toBe('c'); - - $rootScope.predicate = 'id'; - $rootScope.$digest(); - expect(element.find('li:eq(0)').text()).toBe('c'); - expect(element.find('li:eq(1)').text()).toBe('a'); - expect(element.find('li:eq(2)').text()).toBe('b'); + element = $compile('<treecontrol tree-model="treedata" order-by-expression="node.subTreeSortOrder">{{node.label}}</treecontrol>')($rootScope); + $rootScope.$digest(); + element.find('li:eq(1) .tree-branch-head').click(); // expand 'b' sub-tree + element.find('li:eq(0) .tree-branch-head').click(); // expand 'a' sub-tree + expect(element.find('li').length).toBe(8); + + // 'A' sub-tree is ordered by descending label + expect(element.find('li:eq(0) div.tree-label:eq(0)').text()).toBe('a'); + expect(element.find('li:eq(1)').text()).toBe('a3'); + expect(element.find('li:eq(2)').text()).toBe('a2'); + expect(element.find('li:eq(3)').text()).toBe('a1'); + + // 'B' sub-tree is ordered by ascending label + expect(element.find('li:eq(4) div.tree-label:eq(0)').text()).toBe('b'); + expect(element.find('li:eq(5)').text()).toBe('b1'); + expect(element.find('li:eq(6)').text()).toBe('b2'); + expect(element.find('li:eq(7)').text()).toBe('b3'); }); it('should be able to accept alternative children variable name', function () {