Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* Any copyright is dedicated to the Public Domain.
4 http://creativecommons.org/publicdomain/zero/1.0/ */
6 "use strict";
8 //////////////////////////////////////////////////////////////////////////
9 // Test helpers
11 let TopSitesTestHelper = {
12 get grid() {
13 return Browser.selectedBrowser.contentDocument.getElementById("start-topsites-grid");
14 },
15 get document() {
16 return Browser.selectedBrowser.contentDocument;
17 },
18 setup: function() {
19 return Task.spawn(function(){
20 if (BrowserUI.isStartTabVisible)
21 return;
23 yield addTab("about:start");
25 yield waitForCondition(() => BrowserUI.isStartTabVisible);
26 });
27 },
28 mockLinks: function th_mockLinks(aLinks) {
29 // create link objects where index. corresponds to grid position
30 // falsy values are set to null
31 let links = (typeof aLinks == "string") ?
32 aLinks.split(/\s*,\s*/) : aLinks;
34 links = links.map(function (id) {
35 return (id) ? {url: "http://example.com/#" + id, title: id} : null;
36 });
37 return links;
38 },
39 siteFromNode: function th_siteFromNode(aNode) {
40 return {
41 url: aNode.getAttribute("value"),
42 title: aNode.getAttribute("label")
43 }
44 },
45 clearHistory: function th_clearHistory() {
46 PlacesUtils.history.removeAllPages();
47 },
48 fillHistory: function th_fillHistory(aLinks) {
49 return Task.spawn(function(){
50 let numLinks = aLinks.length;
51 let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
53 let updateDeferred = Promise.defer();
55 for (let link of aLinks.reverse()) {
56 let place = {
57 uri: Util.makeURI(link.url),
58 title: link.title,
59 visits: [{visitDate: Date.now() * 1000, transitionType: transitionLink}]
60 };
61 try {
62 PlacesUtils.asyncHistory.updatePlaces(place, {
63 handleError: function (aError) {
64 ok(false, "couldn't add visit to history");
65 throw new Task.Result(aError);
66 },
67 handleResult: function () {},
68 handleCompletion: function () {
69 if(--numLinks <= 0) {
70 updateDeferred.resolve(true);
71 }
72 }
73 });
74 } catch(e) {
75 ok(false, "because: " + e);
76 }
77 }
78 return updateDeferred.promise;
79 });
80 },
82 /**
83 * Allows to specify the list of pinned links (that have a fixed position in
84 * the grid.
85 * @param aLinksPattern the pattern (see below)
86 *
87 * Example: setPinnedLinks("foo,,bar")
88 * Result: 'http://example.com/#foo' is pinned in the first cell. 'http://example.com/#bar' is
89 * pinned in the third cell.
90 */
91 setPinnedLinks: function th_setPinnedLinks(aLinks) {
92 let links = TopSitesTestHelper.mockLinks(aLinks);
94 // (we trust that NewTabUtils works, and test our consumption of it)
95 // clear all existing pins
96 Array.forEach(NewTabUtils.pinnedLinks.links, function(aLink){
97 if(aLink)
98 NewTabUtils.pinnedLinks.unpin(aLink);
99 });
101 links.forEach(function(aLink, aIndex){
102 if(aLink) {
103 NewTabUtils.pinnedLinks.pin(aLink, aIndex);
104 }
105 });
106 NewTabUtils.pinnedLinks.save();
107 },
109 /**
110 * Allows to provide a list of links that is used to construct the grid.
111 * @param aLinksPattern the pattern (see below)
112 * @param aPinnedLinksPattern the pattern (see below)
113 *
114 * Example: setLinks("dougal,florence,zebedee")
115 * Result: [{url: "http://example.com/#dougal", title: "dougal"},
116 * {url: "http://example.com/#florence", title: "florence"}
117 * {url: "http://example.com/#zebedee", title: "zebedee"}]
118 * Example: setLinks("dougal,florence,zebedee","dougal,,zebedee")
119 * Result: http://example.com/#dougal is pinned at index 0, http://example.com/#florence at index 2
120 */
122 setLinks: function th_setLinks(aLinks, aPinnedLinks) {
123 let links = TopSitesTestHelper.mockLinks(aLinks);
124 if (links.filter(function(aLink){
125 return !aLink;
126 }).length) {
127 throw new Error("null link objects in setLinks");
128 }
130 return Task.spawn(function() {
131 TopSitesTestHelper.clearHistory();
133 yield Task.spawn(TopSitesTestHelper.fillHistory(links));
135 if(aPinnedLinks) {
136 TopSitesTestHelper.setPinnedLinks(aPinnedLinks);
137 }
139 // reset the TopSites state, have it update its cache with the new data fillHistory put there
140 yield TopSites.prepareCache(true);
141 });
142 },
144 updatePagesAndWait: function th_updatePagesAndWait() {
145 let deferredUpdate = Promise.defer();
146 let updater = {
147 update: function() {
148 NewTabUtils.allPages.unregister(updater);
149 deferredUpdate.resolve(true);
150 }
151 };
152 NewTabUtils.allPages.register(updater);
153 setTimeout(function() {
154 NewTabUtils.allPages.update();
155 }, 0);
156 return deferredUpdate.promise;
157 },
159 tearDown: function th_tearDown() {
160 TopSitesTestHelper.clearHistory();
161 }
162 };
165 //////////////////////////////////////////////////////////////////////////
168 function test() {
169 registerCleanupFunction(TopSitesTestHelper.tearDown);
170 runTests();
171 }
173 gTests.push({
174 desc: "TopSites dependencies",
175 run: function() {
176 ok(NewTabUtils, "NewTabUtils is truthy");
177 ok(TopSites, "TopSites is truthy");
178 }
179 });
181 gTests.push({
182 desc: "load and display top sites",
183 setUp: function() {
184 yield TopSitesTestHelper.setup();
185 let grid = TopSitesTestHelper.grid;
187 // setup - set history to known state
188 yield TopSitesTestHelper.setLinks("brian,dougal,dylan,ermintrude,florence,moose,sgtsam,train,zebedee,zeebad");
190 let arrangedPromise = waitForEvent(grid, "arranged");
191 yield TopSitesTestHelper.updatePagesAndWait();
192 // pause until the update has fired and the view is finishd updating
193 yield arrangedPromise;
194 },
195 run: function() {
196 let grid = TopSitesTestHelper.grid;
197 let items = grid.items;
198 is(items.length, 8, "should be 8 topsites"); // i.e. not 10
199 if(items.length) {
200 let firstitem = items[0];
201 is(
202 firstitem.getAttribute("label"),
203 "brian",
204 "first item label should be 'brian': " + firstitem.getAttribute("label")
205 );
206 is(
207 firstitem.getAttribute("value"),
208 "http://example.com/#brian",
209 "first item url should be 'http://example.com/#brian': " + firstitem.getAttribute("url")
210 );
211 }
212 }
213 });
215 gTests.push({
216 desc: "pinned sites",
217 pins: "dangermouse,zebedee,,,dougal",
218 setUp: function() {
219 yield TopSitesTestHelper.setup();
220 // setup - set history to known state
221 yield TopSitesTestHelper.setLinks(
222 "brian,dougal,dylan,ermintrude,florence,moose,sgtsam,train,zebedee,zeebad",
223 this.pins
224 );
225 // pause until the update has fired and the view is finishd updating
226 let arrangedPromise = waitForEvent(TopSitesTestHelper.grid, "arranged");
227 yield TopSitesTestHelper.updatePagesAndWait();
228 yield arrangedPromise;
229 },
230 run: function() {
231 // test that pinned state of each site as rendered matches our expectations
232 let pins = this.pins.split(",");
233 let items = TopSitesTestHelper.grid.items;
234 is(items.length, 8, "should be 8 topsites in the grid");
236 is(TopSitesTestHelper.document.querySelectorAll("#start-topsites-grid > [pinned]").length, 3, "should be 3 children with 'pinned' attribute");
237 try {
238 Array.forEach(items, function(aItem, aIndex){
239 // pinned state should agree with the pins array
240 is(
241 aItem.hasAttribute("pinned"), !!pins[aIndex],
242 "site at index " + aIndex + " was " +aItem.hasAttribute("pinned")
243 +", should agree with " + !!pins[aIndex]
244 );
245 if (pins[aIndex]) {
246 is(
247 aItem.getAttribute("label"),
248 pins[aIndex],
249 "pinned site has correct label: " + pins[aIndex] +"=="+ aItem.getAttribute("label")
250 );
251 }
252 }, this);
253 } catch(e) {
254 ok(false, this.desc + ": Test of pinned state on items failed");
255 info("because: " + e.message + "\n" + e.stack);
256 }
257 }
258 });
260 gTests.push({
261 desc: "pin site",
262 setUp: function() {
263 yield TopSitesTestHelper.setup();
264 // setup - set history to known state
265 yield TopSitesTestHelper.setLinks("sgtsam,train,zebedee,zeebad", []); // nothing initially pinned
266 // pause until the update has fired and the view is finishd updating
267 let arrangedPromise = waitForEvent(TopSitesTestHelper.grid, "arranged");
268 yield TopSitesTestHelper.updatePagesAndWait();
269 yield arrangedPromise;
270 },
271 run: function() {
272 // pin a site
273 // test that site is pinned as expected
274 // and that sites fill positions around it
275 let grid = TopSitesTestHelper.grid,
276 items = grid.items;
277 is(items.length, 4, this.desc + ": should be 4 topsites");
279 let tile = grid.items[2],
280 url = tile.getAttribute("value"),
281 title = tile.getAttribute("label");
283 info(this.desc + ": pinning site at index 2");
284 TopSites.pinSites([{
285 url: url,
286 title: title
287 }], [2]);
289 // pinning shouldn't require re-arranging - just wait for isUpdating flag to flip
290 yield waitForCondition(function(){
291 return !grid.controller.isUpdating;
292 });
294 let thirdTile = grid.items[2];
295 ok( thirdTile.hasAttribute("pinned"), thirdTile.getAttribute("value")+ " should look pinned" );
297 // visit some more sites
298 yield TopSitesTestHelper.fillHistory( TopSitesTestHelper.mockLinks("brian,dougal,dylan,ermintrude,florence,moose") );
300 // force flush and repopulation of links cache
301 yield TopSites.prepareCache(true);
302 // pause until the update has fired and the view is finishd updating
303 let arrangedPromise = waitForEvent(grid, "arranged");
304 yield TopSitesTestHelper.updatePagesAndWait();
305 yield arrangedPromise;
307 // check zebedee is still pinned at index 2
308 is( items[2].getAttribute("label"), "zebedee", "Pinned site remained at its index" );
309 ok( items[2].hasAttribute("pinned"), "3rd site should still look pinned" );
310 }
311 });
313 gTests.push({
314 desc: "unpin site",
315 pins: ",zebedee",
316 setUp: function() {
317 yield TopSitesTestHelper.setup();
318 // setup - set history to known state
319 yield TopSitesTestHelper.setLinks(
320 "brian,dougal,dylan,ermintrude,florence,moose,sgtsam,train,zebedee,zeebad",
321 this.pins
322 );
323 // pause until the update has fired and the view is finishd updating
324 let arrangedPromise = waitForEvent(TopSitesTestHelper.grid, "arranged");
325 yield TopSitesTestHelper.updatePagesAndWait();
326 yield arrangedPromise;
327 },
328 run: function() {
329 // unpin a pinned site
330 // test that sites are unpinned as expected
331 let grid = TopSitesTestHelper.grid,
332 items = grid.items;
333 is(items.length, 8, this.desc + ": should be 8 topsites");
334 let site = {
335 url: items[1].getAttribute("value"),
336 title: items[1].getAttribute("label")
337 };
338 // verify assumptions before unpinning this site
339 ok( NewTabUtils.pinnedLinks.isPinned(site), "2nd item is pinned" );
340 ok( items[1].hasAttribute("pinned"), "2nd item has pinned attribute" );
342 // unpinning shouldn't require re-arranging - just wait for isUpdating flag to flip
343 TopSites.unpinSites([site]);
345 yield waitForCondition(function(){
346 return !grid.controller.isUpdating;
347 });
349 let secondTile = grid.items[1];
350 ok( !secondTile.hasAttribute("pinned"), "2nd item should no longer be marked as pinned" );
351 ok( !NewTabUtils.pinnedLinks.isPinned(site), "2nd item should no longer be pinned" );
352 }
353 });
355 gTests.push({
356 desc: "block/unblock sites",
357 setUp: function() {
358 yield TopSitesTestHelper.setup();
359 // setup - set topsites to known state
360 yield TopSitesTestHelper.setLinks(
361 "brian,dougal,dylan,ermintrude,florence,moose,sgtsam,train,zebedee,zeebad,basic,coral",
362 ",dougal"
363 );
364 // pause until the update has fired and the view is finishd updating
365 let arrangedPromise = waitForEvent(TopSitesTestHelper.grid, "arranged");
366 yield TopSitesTestHelper.updatePagesAndWait();
367 yield arrangedPromise;
368 },
369 run: function() {
370 try {
371 // block a site
372 // test that sites are removed from the grid as expected
373 let grid = TopSitesTestHelper.grid,
374 items = grid.items;
375 is(items.length, 8, this.desc + ": should be 8 topsites");
377 let brianSite = TopSitesTestHelper.siteFromNode(items[0]);
378 let dougalSite = TopSitesTestHelper.siteFromNode(items[1]);
379 let dylanSite = TopSitesTestHelper.siteFromNode(items[2]);
381 let arrangedPromise = waitForEvent(grid, "arranged");
382 // we'll block brian (he's not pinned)
383 TopSites.hideSites([brianSite]);
384 // pause until the update has fired and the view is finished updating
385 yield arrangedPromise;
387 // verify brian is blocked and removed from the grid
388 ok( (new Site(brianSite)).blocked, "Site has truthy blocked property" );
389 ok( NewTabUtils.blockedLinks.isBlocked(brianSite), "Site was blocked" );
390 is( grid.querySelectorAll("[value='"+brianSite.url+"']").length, 0, "Blocked site was removed from grid");
392 // make sure the empty slot was backfilled
393 is(items.length, 8, this.desc + ": should be 8 topsites");
395 arrangedPromise = waitForEvent(grid, "arranged");
396 // block dougal,dylan. dougal is currently pinned at index 1
397 TopSites.hideSites([dougalSite, dylanSite]);
398 // pause until the update has fired and the view is finished updating
399 yield arrangedPromise;
401 // verify dougal is blocked and removed from the grid
402 ok( (new Site(dougalSite)).blocked, "Site has truthy blocked property" );
403 ok( NewTabUtils.blockedLinks.isBlocked(dougalSite), "Site was blocked" );
404 ok( !NewTabUtils.pinnedLinks.isPinned(dougalSite), "Blocked Site is no longer pinned" );
405 is( grid.querySelectorAll("[value='"+dougalSite.url+"']").length, 0, "Blocked site was removed from grid");
407 // verify dylan is blocked and removed from the grid
408 ok( (new Site(dylanSite)).blocked, "Site has truthy blocked property" );
409 ok( NewTabUtils.blockedLinks.isBlocked(dylanSite), "Site was blocked" );
410 ok( !NewTabUtils.pinnedLinks.isPinned(dylanSite), "Blocked Site is no longer pinned" );
411 is( grid.querySelectorAll("[value='"+dylanSite.url+"']").length, 0, "Blocked site was removed from grid");
413 // make sure the empty slots were backfilled
414 is(items.length, 8, this.desc + ": should be 8 topsites");
416 arrangedPromise = waitForEvent(grid, "arranged");
417 TopSites.restoreSites([brianSite, dougalSite, dylanSite]);
418 // pause until the update has fired and the view is finished updating
419 yield arrangedPromise;
421 // verify brian, dougal and dyland are unblocked and back in the grid
422 ok( !NewTabUtils.blockedLinks.isBlocked(brianSite), "site was unblocked" );
423 is( grid.querySelectorAll("[value='"+brianSite.url+"']").length, 1, "Unblocked site is back in the grid");
425 ok( !NewTabUtils.blockedLinks.isBlocked(dougalSite), "site was unblocked" );
426 is( grid.querySelectorAll("[value='"+dougalSite.url+"']").length, 1, "Unblocked site is back in the grid");
427 // ..and that a previously pinned site is re-pinned after being blocked, then restored
428 ok( NewTabUtils.pinnedLinks.isPinned(dougalSite), "Restoring previously pinned site makes it pinned again" );
429 is( grid.items[1].getAttribute("value"), dougalSite.url, "Blocked Site restored to pinned index" );
431 ok( !NewTabUtils.blockedLinks.isBlocked(dylanSite), "site was unblocked" );
432 is( grid.querySelectorAll("[value='"+dylanSite.url+"']").length, 1, "Unblocked site is back in the grid");
434 } catch(ex) {
436 ok(false, this.desc+": Caught exception in test: " + ex);
437 info("trace: " + ex.stack);
438 }
439 }
440 });
442 gTests.push({
443 desc: "delete and restore site tiles",
444 pins: "brian",
445 setUp: function() {
446 yield TopSitesTestHelper.setup();
447 // setup - set history to known state
448 yield TopSitesTestHelper.setLinks(
449 "brian,dougal,dylan,ermintrude",
450 this.pins
451 );
452 // pause until the update has fired and the view is finishd updating
453 let arrangedPromise = waitForEvent(TopSitesTestHelper.grid, "arranged");
454 yield TopSitesTestHelper.updatePagesAndWait();
455 yield arrangedPromise;
456 },
457 run: function() {
458 // delete a both pinned and unpinned sites
459 // test that sites are removed from the grid
460 let grid = TopSitesTestHelper.grid,
461 items = grid.items;
462 is(items.length, 4, this.desc + ": should be 4 topsites");
464 let brianTile = grid.querySelector('richgriditem[value$="brian"]');
465 let dougalTile = grid.querySelector('richgriditem[value$="dougal"]')
467 // verify assumptions before deleting sites
468 ok( brianTile, "Tile for Brian was created");
469 ok( dougalTile, "Tile for Dougal was created");
471 let brianSite = TopSitesTestHelper.siteFromNode(brianTile);
472 let dougalSite = TopSitesTestHelper.siteFromNode(dougalTile);
473 ok( NewTabUtils.pinnedLinks.isPinned( brianSite ), "Brian tile is pinned" );
475 // select the 2 tiles
476 grid.toggleItemSelection(brianTile);
477 grid.toggleItemSelection(dougalTile);
478 is(grid.selectedItems.length, 2, "2 tiles were selected");
480 // pause until the update has fired and the view is finishd updating
481 let arrangedPromise = waitForEvent(grid, "arranged");
483 // raise a mock context-action event to trigger deletion of the selection
484 let event = document.createEvent("Events");
485 event.action = "delete";
486 event.initEvent("context-action", true, true); // is cancelable
487 grid.dispatchEvent(event);
489 yield arrangedPromise;
491 // those sites are blocked and their tiles have been removed from the grid?
492 ok( !grid.querySelector('richgriditem[value="'+brianSite.value+'"]'));
493 ok( !grid.querySelector('richgriditem[value="'+dougalSite.value+'"]'));
494 ok( NewTabUtils.blockedLinks.isBlocked(brianSite), "Brian site was blocked" );
495 ok( NewTabUtils.blockedLinks.isBlocked(dougalSite), "Dougal site was blocked" );
496 // with the tiles deleted, selection should be empty
497 is( grid.selectedItems.length, 0, "Gris selection is empty after deletion" );
499 // raise a mock context-action event to trigger restore
500 arrangedPromise = waitForEvent(grid, "arranged");
501 event = document.createEvent("Events");
502 event.action = "restore";
503 event.initEvent("context-action", true, true); // is cancelable
504 grid.dispatchEvent(event);
506 yield arrangedPromise;
507 brianTile = grid.querySelector('richgriditem[value$="brian"]');
508 dougalTile = grid.querySelector('richgriditem[value$="dougal"]');
510 // those tiles have been restored to the grid?
511 ok( brianTile, "First tile was restored to the grid" );
512 ok( dougalTile, "2nd tile was restored to the grid" );
514 is(grid.selectedItems.length, 2, "2 tiles are still selected");
515 is( grid.selectedItems[0], brianTile, "Brian is still selected" );
516 is( grid.selectedItems[1], dougalTile, "Dougal is still selected" );
517 ok( NewTabUtils.pinnedLinks.isPinned( brianSite ), "Brian tile is still pinned" );
518 ok( !NewTabUtils.blockedLinks.isBlocked(brianSite), "Brian site was unblocked" );
519 ok( !NewTabUtils.blockedLinks.isBlocked(dougalSite), "Dougal site was unblocked" );
521 }
522 });