1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/base/tests/mochitest/browser_tiles.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,675 @@ 1.4 +let doc; 1.5 + 1.6 +function test() { 1.7 + waitForExplicitFinish(); 1.8 + Task.spawn(function(){ 1.9 + info(chromeRoot + "browser_tilegrid.xul"); 1.10 + yield addTab(chromeRoot + "browser_tilegrid.xul"); 1.11 + doc = Browser.selectedTab.browser.contentWindow.document; 1.12 + }).then(runTests); 1.13 +} 1.14 + 1.15 +function _checkIfBoundByRichGrid_Item(expected, node, idx) { 1.16 + let binding = node.ownerDocument.defaultView.getComputedStyle(node).MozBinding; 1.17 + let result = ('url("chrome://browser/content/bindings/grid.xml#richgrid-item")' == binding); 1.18 + return (result == expected); 1.19 +} 1.20 +let isBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, true); 1.21 +let isNotBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, false); 1.22 + 1.23 +gTests.push({ 1.24 + desc: "richgrid binding is applied", 1.25 + run: function() { 1.26 + ok(doc, "doc got defined"); 1.27 + 1.28 + let grid = doc.querySelector("#grid1"); 1.29 + ok(grid, "#grid1 is found"); 1.30 + is(typeof grid.clearSelection, "function", "#grid1 has the binding applied"); 1.31 + is(grid.items.length, 2, "#grid1 has a 2 items"); 1.32 + is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'"); 1.33 + ok(Array.every(grid.items, isBoundByRichGrid_Item), "All items are bound by richgrid-item"); 1.34 + } 1.35 +}); 1.36 + 1.37 +gTests.push({ 1.38 + desc: "item clicks are handled", 1.39 + run: function() { 1.40 + let grid = doc.querySelector("#grid1"); 1.41 + is(typeof grid.handleItemClick, "function", "grid.handleItemClick is a function"); 1.42 + let handleStub = stubMethod(grid, 'handleItemClick'); 1.43 + let itemId = "grid1_item1"; // grid.items[0].getAttribute("id"); 1.44 + 1.45 + // send click to item and wait for next tick; 1.46 + EventUtils.sendMouseEvent({type: 'click'}, itemId, doc.defaultView); 1.47 + yield waitForMs(0); 1.48 + 1.49 + is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item"); 1.50 + handleStub.restore(); 1.51 + 1.52 + // if the grid has a controller, it should be called too 1.53 + let gridController = { 1.54 + handleItemClick: function() {} 1.55 + }; 1.56 + let controllerHandleStub = stubMethod(gridController, "handleItemClick"); 1.57 + let origController = grid.controller; 1.58 + grid.controller = gridController; 1.59 + 1.60 + // send click to item and wait for next tick; 1.61 + EventUtils.sendMouseEvent({type: 'click'}, itemId, doc.defaultView); 1.62 + yield waitForMs(0); 1.63 + 1.64 + is(controllerHandleStub.callCount, 1, "controller.handleItemClick was called when we clicked an item"); 1.65 + is(controllerHandleStub.calledWith[0], doc.getElementById(itemId), "controller.handleItemClick was passed the grid item"); 1.66 + grid.controller = origController; 1.67 + } 1.68 +}); 1.69 + 1.70 +gTests.push({ 1.71 + desc: "arrangeItems", 1.72 + run: function() { 1.73 + // implements an arrangeItems method, with optional cols, rows signature 1.74 + let container = doc.getElementById("alayout"); 1.75 + let grid = doc.querySelector("#grid_layout"); 1.76 + 1.77 + is(typeof grid.arrangeItems, "function", "arrangeItems is a function on the grid"); 1.78 + 1.79 + ok(grid.tileHeight, "grid has truthy tileHeight value"); 1.80 + ok(grid.tileWidth, "grid has truthy tileWidth value"); 1.81 + 1.82 + // make the container big enough for 3 rows 1.83 + container.style.height = 3 * grid.tileHeight + 20 + "px"; 1.84 + 1.85 + // add some items 1.86 + grid.appendItem("test title", "about:blank", true); 1.87 + grid.appendItem("test title", "about:blank", true); 1.88 + grid.appendItem("test title", "about:blank", true); 1.89 + grid.appendItem("test title", "about:blank", true); 1.90 + grid.appendItem("test title", "about:blank", true); 1.91 + 1.92 + grid.arrangeItems(); 1.93 + // they should all fit nicely in a 3x2 grid 1.94 + is(grid.rowCount, 3, "rowCount is calculated correctly for a given container height and tileheight"); 1.95 + is(grid.columnCount, 2, "columnCount is calculated correctly for a given container maxWidth and tilewidth"); 1.96 + 1.97 + // squish the available height 1.98 + // should overflow (clip) a 2x2 grid 1.99 + 1.100 + let under3rowsHeight = (3 * grid.tileHeight -20) + "px"; 1.101 + container.style.height = under3rowsHeight; 1.102 + 1.103 + let arrangedPromise = waitForEvent(grid, "arranged"); 1.104 + grid.arrangeItems(); 1.105 + yield arrangedPromise; 1.106 + 1.107 + ok(true, "arranged event is fired when arrangeItems is called"); 1.108 + is(grid.rowCount, 2, "rowCount is re-calculated correctly for a given container height"); 1.109 + } 1.110 +}); 1.111 + 1.112 +gTests.push({ 1.113 + desc: "clearAll", 1.114 + run: function() { 1.115 + let grid = doc.getElementById("clearGrid"); 1.116 + grid.arrangeItems(); 1.117 + 1.118 + // grid has rows=2 so we expect at least 2 rows and 2 columns with 3 items 1.119 + is(typeof grid.clearAll, "function", "clearAll is a function on the grid"); 1.120 + is(grid.itemCount, 3, "grid has 3 items initially"); 1.121 + is(grid.rowCount, 2, "grid has 2 rows initially"); 1.122 + is(grid.columnCount, 2, "grid has 2 cols initially"); 1.123 + 1.124 + let arrangeSpy = spyOnMethod(grid, "arrangeItems"); 1.125 + grid.clearAll(); 1.126 + 1.127 + is(grid.itemCount, 0, "grid has 0 itemCount after clearAll"); 1.128 + is(grid.items.length, 0, "grid has 0 items after clearAll"); 1.129 + // now that we use slots, an empty grid may still have non-zero rows & columns 1.130 + 1.131 + is(arrangeSpy.callCount, 1, "arrangeItems is called once when we clearAll"); 1.132 + arrangeSpy.restore(); 1.133 + } 1.134 +}); 1.135 + 1.136 +gTests.push({ 1.137 + desc: "empty grid", 1.138 + run: function() { 1.139 + // XXX grids have minSlots and may not be ever truly empty 1.140 + 1.141 + let grid = doc.getElementById("emptyGrid"); 1.142 + grid.arrangeItems(); 1.143 + yield waitForCondition(() => !grid.isArranging); 1.144 + 1.145 + // grid has 2 rows, 6 slots, 0 items 1.146 + ok(grid.isBound, "binding was applied"); 1.147 + is(grid.itemCount, 0, "empty grid has 0 items"); 1.148 + // minSlots attr. creates unpopulated slots 1.149 + is(grid.rowCount, grid.getAttribute("rows"), "empty grid with rows-attribute has that number of rows"); 1.150 + is(grid.columnCount, 3, "empty grid has expected number of columns"); 1.151 + 1.152 + // remove rows attribute and allow space for the grid to find its own height 1.153 + // for its number of slots 1.154 + grid.removeAttribute("rows"); 1.155 + grid.parentNode.style.height = 20+(grid.tileHeight*grid.minSlots)+"px"; 1.156 + 1.157 + grid.arrangeItems(); 1.158 + yield waitForCondition(() => !grid.isArranging); 1.159 + is(grid.rowCount, grid.minSlots, "empty grid has this.minSlots rows"); 1.160 + is(grid.columnCount, 1, "empty grid has 1 column"); 1.161 + } 1.162 +}); 1.163 + 1.164 +gTests.push({ 1.165 + desc: "appendItem", 1.166 + run: function() { 1.167 + // implements an appendItem with signature title, uri, returns item element 1.168 + // appendItem triggers arrangeItems 1.169 + let grid = doc.querySelector("#emptygrid"); 1.170 + 1.171 + is(grid.itemCount, 0, "0 itemCount when empty"); 1.172 + is(grid.items.length, 0, "0 items when empty"); 1.173 + is(typeof grid.appendItem, "function", "appendItem is a function on the grid"); 1.174 + 1.175 + let arrangeStub = stubMethod(grid, "arrangeItems"); 1.176 + let newItem = grid.appendItem("test title", "about:blank"); 1.177 + 1.178 + ok(newItem && grid.items[0]==newItem, "appendItem gives back the item"); 1.179 + is(grid.itemCount, 1, "itemCount is incremented when we appendItem"); 1.180 + is(newItem.getAttribute("label"), "test title", "title ends up on label attribute"); 1.181 + is(newItem.getAttribute("value"), "about:blank", "url ends up on value attribute"); 1.182 + 1.183 + is(arrangeStub.callCount, 1, "arrangeItems is called when we appendItem"); 1.184 + arrangeStub.restore(); 1.185 + } 1.186 +}); 1.187 + 1.188 +gTests.push({ 1.189 + desc: "getItemAtIndex", 1.190 + run: function() { 1.191 + // implements a getItemAtIndex method 1.192 + let grid = doc.querySelector("#grid2"); 1.193 + is(typeof grid.getItemAtIndex, "function", "getItemAtIndex is a function on the grid"); 1.194 + is(grid.getItemAtIndex(0).getAttribute("id"), "grid2_item1", "getItemAtIndex retrieves the first item"); 1.195 + is(grid.getItemAtIndex(1).getAttribute("id"), "grid2_item2", "getItemAtIndex item at index 2"); 1.196 + ok(!grid.getItemAtIndex(5), "getItemAtIndex out-of-bounds index returns falsy"); 1.197 + } 1.198 +}); 1.199 + 1.200 +gTests.push({ 1.201 + desc: "removeItemAt", 1.202 + run: function() { 1.203 + // implements a removeItemAt method, with 'index' signature 1.204 + // removeItemAt triggers arrangeItems 1.205 + let grid = doc.querySelector("#grid2"); 1.206 + 1.207 + is(grid.itemCount, 2, "2 items initially"); 1.208 + is(typeof grid.removeItemAt, "function", "removeItemAt is a function on the grid"); 1.209 + 1.210 + let arrangeStub = stubMethod(grid, "arrangeItems"); 1.211 + let removedItem = grid.removeItemAt(0); 1.212 + 1.213 + ok(removedItem, "removeItemAt gives back an item"); 1.214 + is(removedItem.getAttribute("id"), "grid2_item1", "removeItemAt gives back the correct item"); 1.215 + is(grid.items[0].getAttribute("id"), "grid2_item2", "2nd item becomes the first item"); 1.216 + is(grid.itemCount, 1, "itemCount is decremented when we removeItemAt"); 1.217 + 1.218 + is(arrangeStub.callCount, 1, "arrangeItems is called when we removeItemAt"); 1.219 + arrangeStub.restore(); 1.220 + } 1.221 +}); 1.222 + 1.223 +gTests.push({ 1.224 + desc: "insertItemAt", 1.225 + run: function() { 1.226 + // implements an insertItemAt method, with index, title, uri.spec signature 1.227 + // insertItemAt triggers arrangeItems 1.228 + let grid = doc.querySelector("#grid3"); 1.229 + 1.230 + is(grid.itemCount, 2, "2 items initially"); 1.231 + is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid"); 1.232 + 1.233 + let arrangeStub = stubMethod(grid, "arrangeItems"); 1.234 + let insertedAt0 = grid.insertItemAt(0, "inserted item 0", "http://example.com/inserted0"); 1.235 + let insertedAt00 = grid.insertItemAt(0, "inserted item 00", "http://example.com/inserted00"); 1.236 + 1.237 + ok(insertedAt0 && insertedAt00, "insertItemAt gives back an item"); 1.238 + 1.239 + is(insertedAt0.getAttribute("label"), "inserted item 0", "insertItemAt creates item with the correct label"); 1.240 + is(insertedAt0.getAttribute("value"), "http://example.com/inserted0", "insertItemAt creates item with the correct url value"); 1.241 + 1.242 + is(grid.items[0], insertedAt00, "item is inserted at the correct index"); 1.243 + is(grid.children[0], insertedAt00, "first item occupies the first slot"); 1.244 + is(grid.items[1], insertedAt0, "item is inserted at the correct index"); 1.245 + is(grid.children[1], insertedAt0, "next item occupies the next slot"); 1.246 + 1.247 + is(grid.items[2].getAttribute("label"), "First item", "Old first item is now at index 2"); 1.248 + is(grid.items[3].getAttribute("label"), "2nd item", "Old 2nd item is now at index 3"); 1.249 + 1.250 + is(grid.itemCount, 4, "itemCount is incremented when we insertItemAt"); 1.251 + 1.252 + is(arrangeStub.callCount, 2, "arrangeItems is called when we insertItemAt"); 1.253 + arrangeStub.restore(); 1.254 + } 1.255 +}); 1.256 + 1.257 +gTests.push({ 1.258 + desc: "getIndexOfItem", 1.259 + run: function() { 1.260 + // implements a getIndexOfItem method, with item (element) signature 1.261 + // insertItemAt triggers arrangeItems 1.262 + let grid = doc.querySelector("#grid4"); 1.263 + 1.264 + is(grid.itemCount, 2, "2 items initially"); 1.265 + is(typeof grid.getIndexOfItem, "function", "getIndexOfItem is a function on the grid"); 1.266 + 1.267 + let item = doc.getElementById("grid4_item2"); 1.268 + let badItem = doc.createElement("richgriditem"); 1.269 + 1.270 + is(grid.getIndexOfItem(item), 1, "getIndexOfItem returns the correct value for an item"); 1.271 + is(grid.getIndexOfItem(badItem), -1, "getIndexOfItem returns -1 for items it doesn't contain"); 1.272 + } 1.273 +}); 1.274 + 1.275 +gTests.push({ 1.276 + desc: "getItemsByUrl", 1.277 + run: function() { 1.278 + let grid = doc.querySelector("#grid5"); 1.279 + 1.280 + is(grid.itemCount, 4, "4 items total"); 1.281 + is(typeof grid.getItemsByUrl, "function", "getItemsByUrl is a function on the grid"); 1.282 + 1.283 + ['about:blank', 'http://bugzilla.mozilla.org/'].forEach(function(testUrl) { 1.284 + let items = grid.getItemsByUrl(testUrl); 1.285 + is(items.length, 2, "2 matching items in the test grid"); 1.286 + is(items.item(0).url, testUrl, "Matched item has correct url property"); 1.287 + is(items.item(1).url, testUrl, "Matched item has correct url property"); 1.288 + }); 1.289 + 1.290 + let badUrl = 'http://gopher.well.com:70/'; 1.291 + let items = grid.getItemsByUrl(badUrl); 1.292 + is(items.length, 0, "0 items matched url: "+badUrl); 1.293 + 1.294 + } 1.295 +}); 1.296 + 1.297 +gTests.push({ 1.298 + desc: "removeItem", 1.299 + run: function() { 1.300 + let grid = doc.querySelector("#grid5"); 1.301 + 1.302 + is(grid.itemCount, 4, "4 items total"); 1.303 + is(typeof grid.removeItem, "function", "removeItem is a function on the grid"); 1.304 + 1.305 + let arrangeStub = stubMethod(grid, "arrangeItems"); 1.306 + let removedFirst = grid.removeItem( grid.items[0] ); 1.307 + 1.308 + is(arrangeStub.callCount, 1, "arrangeItems is called when we removeItem"); 1.309 + 1.310 + let removed2nd = grid.removeItem( grid.items[0], true); 1.311 + is(removed2nd.getAttribute("label"), "2nd item", "the next item was returned"); 1.312 + is(grid.itemCount, 2, "2 items remain"); 1.313 + 1.314 + // callCount should still be at 1 1.315 + is(arrangeStub.callCount, 1, "arrangeItems is not called when we pass the truthy skipArrange param"); 1.316 + 1.317 + let otherItem = grid.ownerDocument.querySelector("#grid6_item1"); 1.318 + let removedFail = grid.removeItem(otherItem); 1.319 + ok(!removedFail, "Falsy value returned when non-child item passed"); 1.320 + is(grid.itemCount, 2, "2 items remain"); 1.321 + 1.322 + // callCount should still be at 1 1.323 + is(arrangeStub.callCount, 1, "arrangeItems is not called when nothing is matched"); 1.324 + 1.325 + arrangeStub.restore(); 1.326 + } 1.327 +}); 1.328 + 1.329 +gTests.push({ 1.330 + desc: "selections (single)", 1.331 + run: function() { 1.332 + // when seltype is single, 1.333 + // maintains a selectedItem property 1.334 + // maintains a selectedIndex property 1.335 + // clearSelection, selectItem, toggleItemSelection methods are implemented 1.336 + // 'select' events are implemented 1.337 + let grid = doc.querySelector("#grid-select1"); 1.338 + 1.339 + is(typeof grid.clearSelection, "function", "clearSelection is a function on the grid"); 1.340 + is(typeof grid.selectedItems, "object", "selectedItems is a property on the grid"); 1.341 + is(typeof grid.toggleItemSelection, "function", "toggleItemSelection is function on the grid"); 1.342 + is(typeof grid.selectItem, "function", "selectItem is a function on the grid"); 1.343 + 1.344 + is(grid.itemCount, 2, "2 items initially"); 1.345 + is(grid.selectedItems.length, 0, "nothing selected initially"); 1.346 + 1.347 + grid.toggleItemSelection(grid.items[1]); 1.348 + ok(grid.items[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item"); 1.349 + is(grid.selectedIndex, 1, "selectedIndex is correct"); 1.350 + 1.351 + grid.toggleItemSelection(grid.items[1]); 1.352 + ok(!grid.items[1].selected, "toggleItemSelection sets falsy selected prop on previously-selected item"); 1.353 + is(grid.selectedIndex, -1, "selectedIndex reports correctly with nothing selected"); 1.354 + 1.355 + // item selection 1.356 + grid.selectItem(grid.items[1]); 1.357 + ok(grid.items[1].selected, "Item selected property is truthy after grid.selectItem"); 1.358 + ok(grid.items[1].getAttribute("selected"), "Item selected attribute is truthy after grid.selectItem"); 1.359 + ok(grid.selectedItems.length, "There are selectedItems after grid.selectItem"); 1.360 + 1.361 + // select events 1.362 + // in seltype=single mode, select is like the default action for the tile 1.363 + // (think <a>, not <select multiple>) 1.364 + let handler = { 1.365 + handleEvent: function(aEvent) {} 1.366 + }; 1.367 + let handlerStub = stubMethod(handler, "handleEvent"); 1.368 + 1.369 + grid.items[1].selected = true; 1.370 + 1.371 + doc.defaultView.addEventListener("select", handler, false); 1.372 + info("select listener added"); 1.373 + 1.374 + // clearSelection 1.375 + grid.clearSelection(); 1.376 + is(grid.selectedItems.length, 0, "Nothing selected when we clearSelection"); 1.377 + is(grid.selectedIndex, -1, "selectedIndex resets after clearSelection"); 1.378 + is(handlerStub.callCount, 0, "clearSelection should not fire a selectionchange event"); 1.379 + 1.380 + info("calling selectItem, currently it is:" + grid.items[0].selected); 1.381 + // Note: A richgrid in seltype=single mode fires "select" events from selectItem 1.382 + grid.selectItem(grid.items[0]); 1.383 + info("calling selectItem, now it is:" + grid.items[0].selected); 1.384 + yield waitForMs(0); 1.385 + 1.386 + is(handlerStub.callCount, 1, "select event handler was called when we selected an item"); 1.387 + is(handlerStub.calledWith[0].type, "select", "handler got a select event"); 1.388 + is(handlerStub.calledWith[0].target, grid, "select event had the originating grid as the target"); 1.389 + handlerStub.restore(); 1.390 + doc.defaultView.removeEventListener("select", handler, false); 1.391 + } 1.392 +}); 1.393 + 1.394 +gTests.push({ 1.395 + desc: "selections (multiple)", 1.396 + run: function() { 1.397 + // when seltype is multiple, 1.398 + // maintains a selectedItems property 1.399 + // clearSelection, selectItem, toggleItemSelection methods are implemented 1.400 + // 'selectionchange' events are implemented 1.401 + let grid = doc.querySelector("#grid-select2"); 1.402 + 1.403 + is(typeof grid.clearSelection, "function", "clearSelection is a function on the grid"); 1.404 + is(typeof grid.selectedItems, "object", "selectedItems is a property on the grid"); 1.405 + is(typeof grid.toggleItemSelection, "function", "toggleItemSelection is function on the grid"); 1.406 + is(typeof grid.selectItem, "function", "selectItem is a function on the grid"); 1.407 + 1.408 + is(grid.itemCount, 2, "2 items initially"); 1.409 + is(grid.selectedItems.length, 0, "nothing selected initially"); 1.410 + 1.411 + grid.toggleItemSelection(grid.items[1]); 1.412 + ok(grid.items[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item"); 1.413 + is(grid.selectedItems.length, 1, "1 item selected when we first toggleItemSelection"); 1.414 + is(grid.selectedItems[0], grid.items[1], "the right item is selected"); 1.415 + is(grid.selectedIndex, 1, "selectedIndex is correct"); 1.416 + 1.417 + grid.toggleItemSelection(grid.items[1]); 1.418 + is(grid.selectedItems.length, 0, "Nothing selected when we toggleItemSelection again"); 1.419 + 1.420 + // selectionchange events 1.421 + // in seltype=multiple mode, we track selected state on all items 1.422 + // (think <select multiple> not <a>) 1.423 + let handler = { 1.424 + handleEvent: function(aEvent) {} 1.425 + }; 1.426 + let handlerStub = stubMethod(handler, "handleEvent"); 1.427 + doc.defaultView.addEventListener("selectionchange", handler, false); 1.428 + info("selectionchange listener added"); 1.429 + 1.430 + // clearSelection 1.431 + grid.items[0].selected=true; 1.432 + grid.items[1].selected=true; 1.433 + is(grid.selectedItems.length, 2, "Both items are selected before calling clearSelection"); 1.434 + grid.clearSelection(); 1.435 + is(grid.selectedItems.length, 0, "Nothing selected when we clearSelection"); 1.436 + ok(!(grid.items[0].selected || grid.items[1].selected), "selected properties all falsy when we clearSelection"); 1.437 + is(handlerStub.callCount, 0, "clearSelection should not fire a selectionchange event"); 1.438 + 1.439 + info("calling toggleItemSelection, currently it is:" + grid.items[0].selected); 1.440 + // Note: A richgrid in seltype=single mode fires "select" events from selectItem 1.441 + grid.toggleItemSelection(grid.items[0]); 1.442 + info("/calling toggleItemSelection, now it is:" + grid.items[0].selected); 1.443 + yield waitForMs(0); 1.444 + 1.445 + is(handlerStub.callCount, 1, "selectionchange event handler was called when we selected an item"); 1.446 + is(handlerStub.calledWith[0].type, "selectionchange", "handler got a selectionchange event"); 1.447 + is(handlerStub.calledWith[0].target, grid, "select event had the originating grid as the target"); 1.448 + handlerStub.restore(); 1.449 + doc.defaultView.removeEventListener("selectionchange", handler, false); 1.450 + } 1.451 +}); 1.452 + 1.453 +gTests.push({ 1.454 + desc: "selectNone", 1.455 + run: function() { 1.456 + let grid = doc.querySelector("#grid-select2"); 1.457 + 1.458 + is(typeof grid.selectNone, "function", "selectNone is a function on the grid"); 1.459 + 1.460 + is(grid.itemCount, 2, "2 items initially"); 1.461 + 1.462 + // selectNone should fire a selectionchange event 1.463 + let handler = { 1.464 + handleEvent: function(aEvent) {} 1.465 + }; 1.466 + let handlerStub = stubMethod(handler, "handleEvent"); 1.467 + doc.defaultView.addEventListener("selectionchange", handler, false); 1.468 + info("selectionchange listener added"); 1.469 + 1.470 + grid.items[0].selected=true; 1.471 + grid.items[1].selected=true; 1.472 + is(grid.selectedItems.length, 2, "Both items are selected before calling selectNone"); 1.473 + grid.selectNone(); 1.474 + 1.475 + is(grid.selectedItems.length, 0, "Nothing selected when we selectNone"); 1.476 + ok(!(grid.items[0].selected || grid.items[1].selected), "selected properties all falsy when we selectNone"); 1.477 + 1.478 + is(handlerStub.callCount, 1, "selectionchange event handler was called when we selectNone"); 1.479 + is(handlerStub.calledWith[0].type, "selectionchange", "handler got a selectionchange event"); 1.480 + is(handlerStub.calledWith[0].target, grid, "selectionchange event had the originating grid as the target"); 1.481 + handlerStub.restore(); 1.482 + doc.defaultView.removeEventListener("selectionchange", handler, false); 1.483 + } 1.484 +}); 1.485 + 1.486 +function gridSlotsSetup() { 1.487 + let grid = this.grid = doc.createElement("richgrid"); 1.488 + grid.setAttribute("minSlots", 6); 1.489 + doc.documentElement.appendChild(grid); 1.490 + is(grid.ownerDocument, doc, "created grid in the expected document"); 1.491 +} 1.492 +function gridSlotsTearDown() { 1.493 + this.grid && this.grid.parentNode.removeChild(this.grid); 1.494 +} 1.495 + 1.496 +gTests.push({ 1.497 + desc: "richgrid slots init", 1.498 + setUp: gridSlotsSetup, 1.499 + run: function() { 1.500 + let grid = this.grid; 1.501 + // grid is initially populated with empty slots matching the minSlots attribute 1.502 + is(grid.children.length, 6, "minSlots slots are created"); 1.503 + is(grid.itemCount, 0, "slots do not count towards itemCount"); 1.504 + ok(Array.every(grid.children, (node) => node.nodeName == 'richgriditem'), "slots have nodeName richgriditem"); 1.505 + ok(Array.every(grid.children, isNotBoundByRichGrid_Item), "slots aren't bound by the richgrid-item binding"); 1.506 + 1.507 + ok(!grid.isItem(grid.children[0]), "slot fails isItem validation"); 1.508 + }, 1.509 + tearDown: gridSlotsTearDown 1.510 +}); 1.511 + 1.512 +gTests.push({ 1.513 + desc: "richgrid using slots for items", 1.514 + setUp: gridSlotsSetup, // creates grid with minSlots = num. slots = 6 1.515 + run: function() { 1.516 + let grid = this.grid; 1.517 + let numSlots = grid.getAttribute("minSlots"); 1.518 + is(grid.children.length, numSlots); 1.519 + // adding items occupies those slots 1.520 + for (let idx of [0,1,2,3,4,5,6]) { 1.521 + let slot = grid.children[idx]; 1.522 + let item = grid.appendItem("item "+idx, "about:mozilla"); 1.523 + if (idx < numSlots) { 1.524 + is(grid.children.length, numSlots); 1.525 + is(slot, item, "The same node is reused when an item is assigned to a slot"); 1.526 + } else { 1.527 + is(typeof slot, 'undefined'); 1.528 + ok(item); 1.529 + is(grid.children.length, grid.itemCount); 1.530 + } 1.531 + } 1.532 + }, 1.533 + tearDown: gridSlotsTearDown 1.534 +}); 1.535 + 1.536 +gTests.push({ 1.537 + desc: "richgrid assign and release slots", 1.538 + setUp: function(){ 1.539 + info("assign and release slots setUp"); 1.540 + this.grid = doc.getElementById("slots_grid"); 1.541 + this.grid.scrollIntoView(); 1.542 + let rect = this.grid.getBoundingClientRect(); 1.543 + info("slots grid at top: " + rect.top + ", window.pageYOffset: " + doc.defaultView.pageYOffset); 1.544 + }, 1.545 + run: function() { 1.546 + let grid = this.grid; 1.547 + // start with 5 of 6 slots occupied 1.548 + for (let idx of [0,1,2,3,4]) { 1.549 + let item = grid.appendItem("item "+idx, "about:mozilla"); 1.550 + item.setAttribute("id", "test_item_"+idx); 1.551 + } 1.552 + is(grid.itemCount, 5); 1.553 + is(grid.children.length, 6); // see setup, where we init with 6 slots 1.554 + let firstItem = grid.items[0]; 1.555 + 1.556 + ok(firstItem.ownerDocument, "item has ownerDocument"); 1.557 + is(doc, firstItem.ownerDocument, "item's ownerDocument is the document we expect"); 1.558 + 1.559 + is(firstItem, grid.children[0], "Item and assigned slot are one and the same"); 1.560 + is(firstItem.control, grid, "Item is bound and its .control points back at the grid"); 1.561 + 1.562 + // before releasing, the grid should be nofified of clicks on that slot 1.563 + let testWindow = grid.ownerDocument.defaultView; 1.564 + 1.565 + let rect = firstItem.getBoundingClientRect(); 1.566 + { 1.567 + let handleStub = stubMethod(grid, 'handleItemClick'); 1.568 + // send click to item and wait for next tick; 1.569 + sendElementTap(testWindow, firstItem); 1.570 + yield waitForMs(0); 1.571 + 1.572 + is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item"); 1.573 + handleStub.restore(); 1.574 + } 1.575 + // _releaseSlot is semi-private, we don't expect consumers of the binding to call it 1.576 + // but want to be sure it does what we expect 1.577 + grid._releaseSlot(firstItem); 1.578 + 1.579 + is(grid.itemCount, 4, "Releasing a slot gives us one less item"); 1.580 + is(firstItem, grid.children[0],"Released slot is still the same node we started with"); 1.581 + 1.582 + // after releasing, the grid should NOT be nofified of clicks 1.583 + { 1.584 + let handleStub = stubMethod(grid, 'handleItemClick'); 1.585 + // send click to item and wait for next tick; 1.586 + sendElementTap(testWindow, firstItem); 1.587 + yield waitForMs(0); 1.588 + 1.589 + is(handleStub.callCount, 0, "handleItemClick was NOT called when we clicked a released slot"); 1.590 + handleStub.restore(); 1.591 + } 1.592 + 1.593 + ok(!firstItem.mozMatchesSelector("richgriditem[value]"), "Released slot doesn't match binding selector"); 1.594 + ok(isNotBoundByRichGrid_Item(firstItem), "Released slot is no longer bound"); 1.595 + 1.596 + waitForCondition(() => isNotBoundByRichGrid_Item(firstItem)); 1.597 + ok(true, "Slot eventually gets unbound"); 1.598 + is(firstItem, grid.children[0], "Released slot is still at expected index in children collection"); 1.599 + 1.600 + let firstSlot = grid.children[0]; 1.601 + firstItem = grid.insertItemAt(0, "New item 0", "about:blank"); 1.602 + ok(firstItem == grid.items[0], "insertItemAt 0 creates item at expected index"); 1.603 + ok(firstItem == firstSlot, "insertItemAt occupies the released slot with the new item"); 1.604 + is(grid.itemCount, 5); 1.605 + is(grid.children.length, 6); 1.606 + is(firstItem.control, grid,"Item is bound and its .control points back at the grid"); 1.607 + 1.608 + let nextSlotIndex = grid.itemCount; 1.609 + let lastItem = grid.insertItemAt(9, "New item 9", "about:blank"); 1.610 + // Check we don't create sparse collection of items 1.611 + is(lastItem, grid.children[nextSlotIndex], "Item is appended at the next index when an out of bounds index is provided"); 1.612 + is(grid.children.length, 6); 1.613 + is(grid.itemCount, 6); 1.614 + 1.615 + grid.appendItem("one more", "about:blank"); 1.616 + is(grid.children.length, 7); 1.617 + is(grid.itemCount, 7); 1.618 + 1.619 + // clearAll results in slots being emptied 1.620 + grid.clearAll(); 1.621 + is(grid.children.length, 6, "Extra slots are trimmed when we clearAll"); 1.622 + ok(!Array.some(grid.children, (node) => node.hasAttribute("value")), "All slots have no value attribute after clearAll") 1.623 + }, 1.624 + tearDown: gridSlotsTearDown 1.625 +}); 1.626 + 1.627 +gTests.push({ 1.628 + desc: "richgrid slot management", 1.629 + setUp: gridSlotsSetup, 1.630 + run: function() { 1.631 + let grid = this.grid; 1.632 + // populate grid with some items 1.633 + let numSlots = grid.getAttribute("minSlots"); 1.634 + for (let idx of [0,1,2,3,4,5]) { 1.635 + let item = grid.appendItem("item "+idx, "about:mozilla"); 1.636 + } 1.637 + 1.638 + is(grid.itemCount, 6, "Grid setup with 6 items"); 1.639 + is(grid.children.length, 6, "Full grid has the expected number of slots"); 1.640 + 1.641 + // removing an item creates a replacement slot *on the end of the stack* 1.642 + let item = grid.removeItemAt(0); 1.643 + is(item.getAttribute("label"), "item 0", "removeItemAt gives back the populated node"); 1.644 + is(grid.children.length, 6); 1.645 + is(grid.itemCount, 5); 1.646 + is(grid.items[0].getAttribute("label"), "item 1", "removeItemAt removes the node so the nextSibling takes its place"); 1.647 + ok(grid.children[5] && !grid.children[5].hasAttribute("value"), "empty slot is added at the end of the existing children"); 1.648 + 1.649 + let item1 = grid.removeItem(grid.items[0]); 1.650 + is(grid.children.length, 6); 1.651 + is(grid.itemCount, 4); 1.652 + is(grid.items[0].getAttribute("label"), "item 2", "removeItem removes the node so the nextSibling takes its place"); 1.653 + }, 1.654 + tearDown: gridSlotsTearDown 1.655 +}); 1.656 + 1.657 +gTests.push({ 1.658 + desc: "richgrid empty slot selection", 1.659 + setUp: gridSlotsSetup, 1.660 + run: function() { 1.661 + let grid = this.grid; 1.662 + // leave grid empty, it has 6 slots 1.663 + 1.664 + is(grid.itemCount, 0, "Grid setup with 0 items"); 1.665 + is(grid.children.length, 6, "Empty grid has the expected number of slots"); 1.666 + 1.667 + info("slot is initially selected: " + grid.children[0].selected); 1.668 + grid.selectItem(grid.children[0]); 1.669 + info("after selectItem, slot is selected: " + grid.children[0].selected); 1.670 + 1.671 + ok(!grid.children[0].selected, "Attempting to select an empty slot has no effect"); 1.672 + 1.673 + grid.toggleItemSelection(grid.children[0]); 1.674 + ok(!grid.children[0].selected, "Attempting to toggle selection on an empty slot has no effect"); 1.675 + 1.676 + }, 1.677 + tearDown: gridSlotsTearDown 1.678 +});