Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 let doc;
3 function test() {
4 waitForExplicitFinish();
5 Task.spawn(function(){
6 info(chromeRoot + "browser_tilegrid.xul");
7 yield addTab(chromeRoot + "browser_tilegrid.xul");
8 doc = Browser.selectedTab.browser.contentWindow.document;
9 }).then(runTests);
10 }
12 function _checkIfBoundByRichGrid_Item(expected, node, idx) {
13 let binding = node.ownerDocument.defaultView.getComputedStyle(node).MozBinding;
14 let result = ('url("chrome://browser/content/bindings/grid.xml#richgrid-item")' == binding);
15 return (result == expected);
16 }
17 let isBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, true);
18 let isNotBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, false);
20 gTests.push({
21 desc: "richgrid binding is applied",
22 run: function() {
23 ok(doc, "doc got defined");
25 let grid = doc.querySelector("#grid1");
26 ok(grid, "#grid1 is found");
27 is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
28 is(grid.items.length, 2, "#grid1 has a 2 items");
29 is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'");
30 ok(Array.every(grid.items, isBoundByRichGrid_Item), "All items are bound by richgrid-item");
31 }
32 });
34 gTests.push({
35 desc: "item clicks are handled",
36 run: function() {
37 let grid = doc.querySelector("#grid1");
38 is(typeof grid.handleItemClick, "function", "grid.handleItemClick is a function");
39 let handleStub = stubMethod(grid, 'handleItemClick');
40 let itemId = "grid1_item1"; // grid.items[0].getAttribute("id");
42 // send click to item and wait for next tick;
43 EventUtils.sendMouseEvent({type: 'click'}, itemId, doc.defaultView);
44 yield waitForMs(0);
46 is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item");
47 handleStub.restore();
49 // if the grid has a controller, it should be called too
50 let gridController = {
51 handleItemClick: function() {}
52 };
53 let controllerHandleStub = stubMethod(gridController, "handleItemClick");
54 let origController = grid.controller;
55 grid.controller = gridController;
57 // send click to item and wait for next tick;
58 EventUtils.sendMouseEvent({type: 'click'}, itemId, doc.defaultView);
59 yield waitForMs(0);
61 is(controllerHandleStub.callCount, 1, "controller.handleItemClick was called when we clicked an item");
62 is(controllerHandleStub.calledWith[0], doc.getElementById(itemId), "controller.handleItemClick was passed the grid item");
63 grid.controller = origController;
64 }
65 });
67 gTests.push({
68 desc: "arrangeItems",
69 run: function() {
70 // implements an arrangeItems method, with optional cols, rows signature
71 let container = doc.getElementById("alayout");
72 let grid = doc.querySelector("#grid_layout");
74 is(typeof grid.arrangeItems, "function", "arrangeItems is a function on the grid");
76 ok(grid.tileHeight, "grid has truthy tileHeight value");
77 ok(grid.tileWidth, "grid has truthy tileWidth value");
79 // make the container big enough for 3 rows
80 container.style.height = 3 * grid.tileHeight + 20 + "px";
82 // add some items
83 grid.appendItem("test title", "about:blank", true);
84 grid.appendItem("test title", "about:blank", true);
85 grid.appendItem("test title", "about:blank", true);
86 grid.appendItem("test title", "about:blank", true);
87 grid.appendItem("test title", "about:blank", true);
89 grid.arrangeItems();
90 // they should all fit nicely in a 3x2 grid
91 is(grid.rowCount, 3, "rowCount is calculated correctly for a given container height and tileheight");
92 is(grid.columnCount, 2, "columnCount is calculated correctly for a given container maxWidth and tilewidth");
94 // squish the available height
95 // should overflow (clip) a 2x2 grid
97 let under3rowsHeight = (3 * grid.tileHeight -20) + "px";
98 container.style.height = under3rowsHeight;
100 let arrangedPromise = waitForEvent(grid, "arranged");
101 grid.arrangeItems();
102 yield arrangedPromise;
104 ok(true, "arranged event is fired when arrangeItems is called");
105 is(grid.rowCount, 2, "rowCount is re-calculated correctly for a given container height");
106 }
107 });
109 gTests.push({
110 desc: "clearAll",
111 run: function() {
112 let grid = doc.getElementById("clearGrid");
113 grid.arrangeItems();
115 // grid has rows=2 so we expect at least 2 rows and 2 columns with 3 items
116 is(typeof grid.clearAll, "function", "clearAll is a function on the grid");
117 is(grid.itemCount, 3, "grid has 3 items initially");
118 is(grid.rowCount, 2, "grid has 2 rows initially");
119 is(grid.columnCount, 2, "grid has 2 cols initially");
121 let arrangeSpy = spyOnMethod(grid, "arrangeItems");
122 grid.clearAll();
124 is(grid.itemCount, 0, "grid has 0 itemCount after clearAll");
125 is(grid.items.length, 0, "grid has 0 items after clearAll");
126 // now that we use slots, an empty grid may still have non-zero rows & columns
128 is(arrangeSpy.callCount, 1, "arrangeItems is called once when we clearAll");
129 arrangeSpy.restore();
130 }
131 });
133 gTests.push({
134 desc: "empty grid",
135 run: function() {
136 // XXX grids have minSlots and may not be ever truly empty
138 let grid = doc.getElementById("emptyGrid");
139 grid.arrangeItems();
140 yield waitForCondition(() => !grid.isArranging);
142 // grid has 2 rows, 6 slots, 0 items
143 ok(grid.isBound, "binding was applied");
144 is(grid.itemCount, 0, "empty grid has 0 items");
145 // minSlots attr. creates unpopulated slots
146 is(grid.rowCount, grid.getAttribute("rows"), "empty grid with rows-attribute has that number of rows");
147 is(grid.columnCount, 3, "empty grid has expected number of columns");
149 // remove rows attribute and allow space for the grid to find its own height
150 // for its number of slots
151 grid.removeAttribute("rows");
152 grid.parentNode.style.height = 20+(grid.tileHeight*grid.minSlots)+"px";
154 grid.arrangeItems();
155 yield waitForCondition(() => !grid.isArranging);
156 is(grid.rowCount, grid.minSlots, "empty grid has this.minSlots rows");
157 is(grid.columnCount, 1, "empty grid has 1 column");
158 }
159 });
161 gTests.push({
162 desc: "appendItem",
163 run: function() {
164 // implements an appendItem with signature title, uri, returns item element
165 // appendItem triggers arrangeItems
166 let grid = doc.querySelector("#emptygrid");
168 is(grid.itemCount, 0, "0 itemCount when empty");
169 is(grid.items.length, 0, "0 items when empty");
170 is(typeof grid.appendItem, "function", "appendItem is a function on the grid");
172 let arrangeStub = stubMethod(grid, "arrangeItems");
173 let newItem = grid.appendItem("test title", "about:blank");
175 ok(newItem && grid.items[0]==newItem, "appendItem gives back the item");
176 is(grid.itemCount, 1, "itemCount is incremented when we appendItem");
177 is(newItem.getAttribute("label"), "test title", "title ends up on label attribute");
178 is(newItem.getAttribute("value"), "about:blank", "url ends up on value attribute");
180 is(arrangeStub.callCount, 1, "arrangeItems is called when we appendItem");
181 arrangeStub.restore();
182 }
183 });
185 gTests.push({
186 desc: "getItemAtIndex",
187 run: function() {
188 // implements a getItemAtIndex method
189 let grid = doc.querySelector("#grid2");
190 is(typeof grid.getItemAtIndex, "function", "getItemAtIndex is a function on the grid");
191 is(grid.getItemAtIndex(0).getAttribute("id"), "grid2_item1", "getItemAtIndex retrieves the first item");
192 is(grid.getItemAtIndex(1).getAttribute("id"), "grid2_item2", "getItemAtIndex item at index 2");
193 ok(!grid.getItemAtIndex(5), "getItemAtIndex out-of-bounds index returns falsy");
194 }
195 });
197 gTests.push({
198 desc: "removeItemAt",
199 run: function() {
200 // implements a removeItemAt method, with 'index' signature
201 // removeItemAt triggers arrangeItems
202 let grid = doc.querySelector("#grid2");
204 is(grid.itemCount, 2, "2 items initially");
205 is(typeof grid.removeItemAt, "function", "removeItemAt is a function on the grid");
207 let arrangeStub = stubMethod(grid, "arrangeItems");
208 let removedItem = grid.removeItemAt(0);
210 ok(removedItem, "removeItemAt gives back an item");
211 is(removedItem.getAttribute("id"), "grid2_item1", "removeItemAt gives back the correct item");
212 is(grid.items[0].getAttribute("id"), "grid2_item2", "2nd item becomes the first item");
213 is(grid.itemCount, 1, "itemCount is decremented when we removeItemAt");
215 is(arrangeStub.callCount, 1, "arrangeItems is called when we removeItemAt");
216 arrangeStub.restore();
217 }
218 });
220 gTests.push({
221 desc: "insertItemAt",
222 run: function() {
223 // implements an insertItemAt method, with index, title, uri.spec signature
224 // insertItemAt triggers arrangeItems
225 let grid = doc.querySelector("#grid3");
227 is(grid.itemCount, 2, "2 items initially");
228 is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid");
230 let arrangeStub = stubMethod(grid, "arrangeItems");
231 let insertedAt0 = grid.insertItemAt(0, "inserted item 0", "http://example.com/inserted0");
232 let insertedAt00 = grid.insertItemAt(0, "inserted item 00", "http://example.com/inserted00");
234 ok(insertedAt0 && insertedAt00, "insertItemAt gives back an item");
236 is(insertedAt0.getAttribute("label"), "inserted item 0", "insertItemAt creates item with the correct label");
237 is(insertedAt0.getAttribute("value"), "http://example.com/inserted0", "insertItemAt creates item with the correct url value");
239 is(grid.items[0], insertedAt00, "item is inserted at the correct index");
240 is(grid.children[0], insertedAt00, "first item occupies the first slot");
241 is(grid.items[1], insertedAt0, "item is inserted at the correct index");
242 is(grid.children[1], insertedAt0, "next item occupies the next slot");
244 is(grid.items[2].getAttribute("label"), "First item", "Old first item is now at index 2");
245 is(grid.items[3].getAttribute("label"), "2nd item", "Old 2nd item is now at index 3");
247 is(grid.itemCount, 4, "itemCount is incremented when we insertItemAt");
249 is(arrangeStub.callCount, 2, "arrangeItems is called when we insertItemAt");
250 arrangeStub.restore();
251 }
252 });
254 gTests.push({
255 desc: "getIndexOfItem",
256 run: function() {
257 // implements a getIndexOfItem method, with item (element) signature
258 // insertItemAt triggers arrangeItems
259 let grid = doc.querySelector("#grid4");
261 is(grid.itemCount, 2, "2 items initially");
262 is(typeof grid.getIndexOfItem, "function", "getIndexOfItem is a function on the grid");
264 let item = doc.getElementById("grid4_item2");
265 let badItem = doc.createElement("richgriditem");
267 is(grid.getIndexOfItem(item), 1, "getIndexOfItem returns the correct value for an item");
268 is(grid.getIndexOfItem(badItem), -1, "getIndexOfItem returns -1 for items it doesn't contain");
269 }
270 });
272 gTests.push({
273 desc: "getItemsByUrl",
274 run: function() {
275 let grid = doc.querySelector("#grid5");
277 is(grid.itemCount, 4, "4 items total");
278 is(typeof grid.getItemsByUrl, "function", "getItemsByUrl is a function on the grid");
280 ['about:blank', 'http://bugzilla.mozilla.org/'].forEach(function(testUrl) {
281 let items = grid.getItemsByUrl(testUrl);
282 is(items.length, 2, "2 matching items in the test grid");
283 is(items.item(0).url, testUrl, "Matched item has correct url property");
284 is(items.item(1).url, testUrl, "Matched item has correct url property");
285 });
287 let badUrl = 'http://gopher.well.com:70/';
288 let items = grid.getItemsByUrl(badUrl);
289 is(items.length, 0, "0 items matched url: "+badUrl);
291 }
292 });
294 gTests.push({
295 desc: "removeItem",
296 run: function() {
297 let grid = doc.querySelector("#grid5");
299 is(grid.itemCount, 4, "4 items total");
300 is(typeof grid.removeItem, "function", "removeItem is a function on the grid");
302 let arrangeStub = stubMethod(grid, "arrangeItems");
303 let removedFirst = grid.removeItem( grid.items[0] );
305 is(arrangeStub.callCount, 1, "arrangeItems is called when we removeItem");
307 let removed2nd = grid.removeItem( grid.items[0], true);
308 is(removed2nd.getAttribute("label"), "2nd item", "the next item was returned");
309 is(grid.itemCount, 2, "2 items remain");
311 // callCount should still be at 1
312 is(arrangeStub.callCount, 1, "arrangeItems is not called when we pass the truthy skipArrange param");
314 let otherItem = grid.ownerDocument.querySelector("#grid6_item1");
315 let removedFail = grid.removeItem(otherItem);
316 ok(!removedFail, "Falsy value returned when non-child item passed");
317 is(grid.itemCount, 2, "2 items remain");
319 // callCount should still be at 1
320 is(arrangeStub.callCount, 1, "arrangeItems is not called when nothing is matched");
322 arrangeStub.restore();
323 }
324 });
326 gTests.push({
327 desc: "selections (single)",
328 run: function() {
329 // when seltype is single,
330 // maintains a selectedItem property
331 // maintains a selectedIndex property
332 // clearSelection, selectItem, toggleItemSelection methods are implemented
333 // 'select' events are implemented
334 let grid = doc.querySelector("#grid-select1");
336 is(typeof grid.clearSelection, "function", "clearSelection is a function on the grid");
337 is(typeof grid.selectedItems, "object", "selectedItems is a property on the grid");
338 is(typeof grid.toggleItemSelection, "function", "toggleItemSelection is function on the grid");
339 is(typeof grid.selectItem, "function", "selectItem is a function on the grid");
341 is(grid.itemCount, 2, "2 items initially");
342 is(grid.selectedItems.length, 0, "nothing selected initially");
344 grid.toggleItemSelection(grid.items[1]);
345 ok(grid.items[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item");
346 is(grid.selectedIndex, 1, "selectedIndex is correct");
348 grid.toggleItemSelection(grid.items[1]);
349 ok(!grid.items[1].selected, "toggleItemSelection sets falsy selected prop on previously-selected item");
350 is(grid.selectedIndex, -1, "selectedIndex reports correctly with nothing selected");
352 // item selection
353 grid.selectItem(grid.items[1]);
354 ok(grid.items[1].selected, "Item selected property is truthy after grid.selectItem");
355 ok(grid.items[1].getAttribute("selected"), "Item selected attribute is truthy after grid.selectItem");
356 ok(grid.selectedItems.length, "There are selectedItems after grid.selectItem");
358 // select events
359 // in seltype=single mode, select is like the default action for the tile
360 // (think <a>, not <select multiple>)
361 let handler = {
362 handleEvent: function(aEvent) {}
363 };
364 let handlerStub = stubMethod(handler, "handleEvent");
366 grid.items[1].selected = true;
368 doc.defaultView.addEventListener("select", handler, false);
369 info("select listener added");
371 // clearSelection
372 grid.clearSelection();
373 is(grid.selectedItems.length, 0, "Nothing selected when we clearSelection");
374 is(grid.selectedIndex, -1, "selectedIndex resets after clearSelection");
375 is(handlerStub.callCount, 0, "clearSelection should not fire a selectionchange event");
377 info("calling selectItem, currently it is:" + grid.items[0].selected);
378 // Note: A richgrid in seltype=single mode fires "select" events from selectItem
379 grid.selectItem(grid.items[0]);
380 info("calling selectItem, now it is:" + grid.items[0].selected);
381 yield waitForMs(0);
383 is(handlerStub.callCount, 1, "select event handler was called when we selected an item");
384 is(handlerStub.calledWith[0].type, "select", "handler got a select event");
385 is(handlerStub.calledWith[0].target, grid, "select event had the originating grid as the target");
386 handlerStub.restore();
387 doc.defaultView.removeEventListener("select", handler, false);
388 }
389 });
391 gTests.push({
392 desc: "selections (multiple)",
393 run: function() {
394 // when seltype is multiple,
395 // maintains a selectedItems property
396 // clearSelection, selectItem, toggleItemSelection methods are implemented
397 // 'selectionchange' events are implemented
398 let grid = doc.querySelector("#grid-select2");
400 is(typeof grid.clearSelection, "function", "clearSelection is a function on the grid");
401 is(typeof grid.selectedItems, "object", "selectedItems is a property on the grid");
402 is(typeof grid.toggleItemSelection, "function", "toggleItemSelection is function on the grid");
403 is(typeof grid.selectItem, "function", "selectItem is a function on the grid");
405 is(grid.itemCount, 2, "2 items initially");
406 is(grid.selectedItems.length, 0, "nothing selected initially");
408 grid.toggleItemSelection(grid.items[1]);
409 ok(grid.items[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item");
410 is(grid.selectedItems.length, 1, "1 item selected when we first toggleItemSelection");
411 is(grid.selectedItems[0], grid.items[1], "the right item is selected");
412 is(grid.selectedIndex, 1, "selectedIndex is correct");
414 grid.toggleItemSelection(grid.items[1]);
415 is(grid.selectedItems.length, 0, "Nothing selected when we toggleItemSelection again");
417 // selectionchange events
418 // in seltype=multiple mode, we track selected state on all items
419 // (think <select multiple> not <a>)
420 let handler = {
421 handleEvent: function(aEvent) {}
422 };
423 let handlerStub = stubMethod(handler, "handleEvent");
424 doc.defaultView.addEventListener("selectionchange", handler, false);
425 info("selectionchange listener added");
427 // clearSelection
428 grid.items[0].selected=true;
429 grid.items[1].selected=true;
430 is(grid.selectedItems.length, 2, "Both items are selected before calling clearSelection");
431 grid.clearSelection();
432 is(grid.selectedItems.length, 0, "Nothing selected when we clearSelection");
433 ok(!(grid.items[0].selected || grid.items[1].selected), "selected properties all falsy when we clearSelection");
434 is(handlerStub.callCount, 0, "clearSelection should not fire a selectionchange event");
436 info("calling toggleItemSelection, currently it is:" + grid.items[0].selected);
437 // Note: A richgrid in seltype=single mode fires "select" events from selectItem
438 grid.toggleItemSelection(grid.items[0]);
439 info("/calling toggleItemSelection, now it is:" + grid.items[0].selected);
440 yield waitForMs(0);
442 is(handlerStub.callCount, 1, "selectionchange event handler was called when we selected an item");
443 is(handlerStub.calledWith[0].type, "selectionchange", "handler got a selectionchange event");
444 is(handlerStub.calledWith[0].target, grid, "select event had the originating grid as the target");
445 handlerStub.restore();
446 doc.defaultView.removeEventListener("selectionchange", handler, false);
447 }
448 });
450 gTests.push({
451 desc: "selectNone",
452 run: function() {
453 let grid = doc.querySelector("#grid-select2");
455 is(typeof grid.selectNone, "function", "selectNone is a function on the grid");
457 is(grid.itemCount, 2, "2 items initially");
459 // selectNone should fire a selectionchange event
460 let handler = {
461 handleEvent: function(aEvent) {}
462 };
463 let handlerStub = stubMethod(handler, "handleEvent");
464 doc.defaultView.addEventListener("selectionchange", handler, false);
465 info("selectionchange listener added");
467 grid.items[0].selected=true;
468 grid.items[1].selected=true;
469 is(grid.selectedItems.length, 2, "Both items are selected before calling selectNone");
470 grid.selectNone();
472 is(grid.selectedItems.length, 0, "Nothing selected when we selectNone");
473 ok(!(grid.items[0].selected || grid.items[1].selected), "selected properties all falsy when we selectNone");
475 is(handlerStub.callCount, 1, "selectionchange event handler was called when we selectNone");
476 is(handlerStub.calledWith[0].type, "selectionchange", "handler got a selectionchange event");
477 is(handlerStub.calledWith[0].target, grid, "selectionchange event had the originating grid as the target");
478 handlerStub.restore();
479 doc.defaultView.removeEventListener("selectionchange", handler, false);
480 }
481 });
483 function gridSlotsSetup() {
484 let grid = this.grid = doc.createElement("richgrid");
485 grid.setAttribute("minSlots", 6);
486 doc.documentElement.appendChild(grid);
487 is(grid.ownerDocument, doc, "created grid in the expected document");
488 }
489 function gridSlotsTearDown() {
490 this.grid && this.grid.parentNode.removeChild(this.grid);
491 }
493 gTests.push({
494 desc: "richgrid slots init",
495 setUp: gridSlotsSetup,
496 run: function() {
497 let grid = this.grid;
498 // grid is initially populated with empty slots matching the minSlots attribute
499 is(grid.children.length, 6, "minSlots slots are created");
500 is(grid.itemCount, 0, "slots do not count towards itemCount");
501 ok(Array.every(grid.children, (node) => node.nodeName == 'richgriditem'), "slots have nodeName richgriditem");
502 ok(Array.every(grid.children, isNotBoundByRichGrid_Item), "slots aren't bound by the richgrid-item binding");
504 ok(!grid.isItem(grid.children[0]), "slot fails isItem validation");
505 },
506 tearDown: gridSlotsTearDown
507 });
509 gTests.push({
510 desc: "richgrid using slots for items",
511 setUp: gridSlotsSetup, // creates grid with minSlots = num. slots = 6
512 run: function() {
513 let grid = this.grid;
514 let numSlots = grid.getAttribute("minSlots");
515 is(grid.children.length, numSlots);
516 // adding items occupies those slots
517 for (let idx of [0,1,2,3,4,5,6]) {
518 let slot = grid.children[idx];
519 let item = grid.appendItem("item "+idx, "about:mozilla");
520 if (idx < numSlots) {
521 is(grid.children.length, numSlots);
522 is(slot, item, "The same node is reused when an item is assigned to a slot");
523 } else {
524 is(typeof slot, 'undefined');
525 ok(item);
526 is(grid.children.length, grid.itemCount);
527 }
528 }
529 },
530 tearDown: gridSlotsTearDown
531 });
533 gTests.push({
534 desc: "richgrid assign and release slots",
535 setUp: function(){
536 info("assign and release slots setUp");
537 this.grid = doc.getElementById("slots_grid");
538 this.grid.scrollIntoView();
539 let rect = this.grid.getBoundingClientRect();
540 info("slots grid at top: " + rect.top + ", window.pageYOffset: " + doc.defaultView.pageYOffset);
541 },
542 run: function() {
543 let grid = this.grid;
544 // start with 5 of 6 slots occupied
545 for (let idx of [0,1,2,3,4]) {
546 let item = grid.appendItem("item "+idx, "about:mozilla");
547 item.setAttribute("id", "test_item_"+idx);
548 }
549 is(grid.itemCount, 5);
550 is(grid.children.length, 6); // see setup, where we init with 6 slots
551 let firstItem = grid.items[0];
553 ok(firstItem.ownerDocument, "item has ownerDocument");
554 is(doc, firstItem.ownerDocument, "item's ownerDocument is the document we expect");
556 is(firstItem, grid.children[0], "Item and assigned slot are one and the same");
557 is(firstItem.control, grid, "Item is bound and its .control points back at the grid");
559 // before releasing, the grid should be nofified of clicks on that slot
560 let testWindow = grid.ownerDocument.defaultView;
562 let rect = firstItem.getBoundingClientRect();
563 {
564 let handleStub = stubMethod(grid, 'handleItemClick');
565 // send click to item and wait for next tick;
566 sendElementTap(testWindow, firstItem);
567 yield waitForMs(0);
569 is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item");
570 handleStub.restore();
571 }
572 // _releaseSlot is semi-private, we don't expect consumers of the binding to call it
573 // but want to be sure it does what we expect
574 grid._releaseSlot(firstItem);
576 is(grid.itemCount, 4, "Releasing a slot gives us one less item");
577 is(firstItem, grid.children[0],"Released slot is still the same node we started with");
579 // after releasing, the grid should NOT be nofified of clicks
580 {
581 let handleStub = stubMethod(grid, 'handleItemClick');
582 // send click to item and wait for next tick;
583 sendElementTap(testWindow, firstItem);
584 yield waitForMs(0);
586 is(handleStub.callCount, 0, "handleItemClick was NOT called when we clicked a released slot");
587 handleStub.restore();
588 }
590 ok(!firstItem.mozMatchesSelector("richgriditem[value]"), "Released slot doesn't match binding selector");
591 ok(isNotBoundByRichGrid_Item(firstItem), "Released slot is no longer bound");
593 waitForCondition(() => isNotBoundByRichGrid_Item(firstItem));
594 ok(true, "Slot eventually gets unbound");
595 is(firstItem, grid.children[0], "Released slot is still at expected index in children collection");
597 let firstSlot = grid.children[0];
598 firstItem = grid.insertItemAt(0, "New item 0", "about:blank");
599 ok(firstItem == grid.items[0], "insertItemAt 0 creates item at expected index");
600 ok(firstItem == firstSlot, "insertItemAt occupies the released slot with the new item");
601 is(grid.itemCount, 5);
602 is(grid.children.length, 6);
603 is(firstItem.control, grid,"Item is bound and its .control points back at the grid");
605 let nextSlotIndex = grid.itemCount;
606 let lastItem = grid.insertItemAt(9, "New item 9", "about:blank");
607 // Check we don't create sparse collection of items
608 is(lastItem, grid.children[nextSlotIndex], "Item is appended at the next index when an out of bounds index is provided");
609 is(grid.children.length, 6);
610 is(grid.itemCount, 6);
612 grid.appendItem("one more", "about:blank");
613 is(grid.children.length, 7);
614 is(grid.itemCount, 7);
616 // clearAll results in slots being emptied
617 grid.clearAll();
618 is(grid.children.length, 6, "Extra slots are trimmed when we clearAll");
619 ok(!Array.some(grid.children, (node) => node.hasAttribute("value")), "All slots have no value attribute after clearAll")
620 },
621 tearDown: gridSlotsTearDown
622 });
624 gTests.push({
625 desc: "richgrid slot management",
626 setUp: gridSlotsSetup,
627 run: function() {
628 let grid = this.grid;
629 // populate grid with some items
630 let numSlots = grid.getAttribute("minSlots");
631 for (let idx of [0,1,2,3,4,5]) {
632 let item = grid.appendItem("item "+idx, "about:mozilla");
633 }
635 is(grid.itemCount, 6, "Grid setup with 6 items");
636 is(grid.children.length, 6, "Full grid has the expected number of slots");
638 // removing an item creates a replacement slot *on the end of the stack*
639 let item = grid.removeItemAt(0);
640 is(item.getAttribute("label"), "item 0", "removeItemAt gives back the populated node");
641 is(grid.children.length, 6);
642 is(grid.itemCount, 5);
643 is(grid.items[0].getAttribute("label"), "item 1", "removeItemAt removes the node so the nextSibling takes its place");
644 ok(grid.children[5] && !grid.children[5].hasAttribute("value"), "empty slot is added at the end of the existing children");
646 let item1 = grid.removeItem(grid.items[0]);
647 is(grid.children.length, 6);
648 is(grid.itemCount, 4);
649 is(grid.items[0].getAttribute("label"), "item 2", "removeItem removes the node so the nextSibling takes its place");
650 },
651 tearDown: gridSlotsTearDown
652 });
654 gTests.push({
655 desc: "richgrid empty slot selection",
656 setUp: gridSlotsSetup,
657 run: function() {
658 let grid = this.grid;
659 // leave grid empty, it has 6 slots
661 is(grid.itemCount, 0, "Grid setup with 0 items");
662 is(grid.children.length, 6, "Empty grid has the expected number of slots");
664 info("slot is initially selected: " + grid.children[0].selected);
665 grid.selectItem(grid.children[0]);
666 info("after selectItem, slot is selected: " + grid.children[0].selected);
668 ok(!grid.children[0].selected, "Attempting to select an empty slot has no effect");
670 grid.toggleItemSelection(grid.children[0]);
671 ok(!grid.children[0].selected, "Attempting to toggle selection on an empty slot has no effect");
673 },
674 tearDown: gridSlotsTearDown
675 });