toolkit/components/jsdownloads/test/unit/test_DownloadList.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:4b23a82d7e48
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/ */
5
6 /**
7 * Tests the DownloadList object.
8 */
9
10 "use strict";
11
12 ////////////////////////////////////////////////////////////////////////////////
13 //// Globals
14
15 /**
16 * Returns a PRTime in the past usable to add expirable visits.
17 *
18 * @note Expiration ignores any visit added in the last 7 days, but it's
19 * better be safe against DST issues, by going back one day more.
20 */
21 function getExpirablePRTime()
22 {
23 let dateObj = new Date();
24 // Normalize to midnight
25 dateObj.setHours(0);
26 dateObj.setMinutes(0);
27 dateObj.setSeconds(0);
28 dateObj.setMilliseconds(0);
29 dateObj = new Date(dateObj.getTime() - 8 * 86400000);
30 return dateObj.getTime() * 1000;
31 }
32
33 /**
34 * Adds an expirable history visit for a download.
35 *
36 * @param aSourceUrl
37 * String containing the URI for the download source, or null to use
38 * httpUrl("source.txt").
39 *
40 * @return {Promise}
41 * @rejects JavaScript exception.
42 */
43 function promiseExpirableDownloadVisit(aSourceUrl)
44 {
45 let deferred = Promise.defer();
46 PlacesUtils.asyncHistory.updatePlaces(
47 {
48 uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")),
49 visits: [{
50 transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
51 visitDate: getExpirablePRTime(),
52 }]
53 },
54 {
55 handleError: function handleError(aResultCode, aPlaceInfo) {
56 let ex = new Components.Exception("Unexpected error in adding visits.",
57 aResultCode);
58 deferred.reject(ex);
59 },
60 handleResult: function () {},
61 handleCompletion: function handleCompletion() {
62 deferred.resolve();
63 }
64 });
65 return deferred.promise;
66 }
67
68 ////////////////////////////////////////////////////////////////////////////////
69 //// Tests
70
71 /**
72 * Checks the testing mechanism used to build different download lists.
73 */
74 add_task(function test_construction()
75 {
76 let downloadListOne = yield promiseNewList();
77 let downloadListTwo = yield promiseNewList();
78 let privateDownloadListOne = yield promiseNewList(true);
79 let privateDownloadListTwo = yield promiseNewList(true);
80
81 do_check_neq(downloadListOne, downloadListTwo);
82 do_check_neq(privateDownloadListOne, privateDownloadListTwo);
83 do_check_neq(downloadListOne, privateDownloadListOne);
84 });
85
86 /**
87 * Checks the methods to add and retrieve items from the list.
88 */
89 add_task(function test_add_getAll()
90 {
91 let list = yield promiseNewList();
92
93 let downloadOne = yield promiseNewDownload();
94 yield list.add(downloadOne);
95
96 let itemsOne = yield list.getAll();
97 do_check_eq(itemsOne.length, 1);
98 do_check_eq(itemsOne[0], downloadOne);
99
100 let downloadTwo = yield promiseNewDownload();
101 yield list.add(downloadTwo);
102
103 let itemsTwo = yield list.getAll();
104 do_check_eq(itemsTwo.length, 2);
105 do_check_eq(itemsTwo[0], downloadOne);
106 do_check_eq(itemsTwo[1], downloadTwo);
107
108 // The first snapshot should not have been modified.
109 do_check_eq(itemsOne.length, 1);
110 });
111
112 /**
113 * Checks the method to remove items from the list.
114 */
115 add_task(function test_remove()
116 {
117 let list = yield promiseNewList();
118
119 yield list.add(yield promiseNewDownload());
120 yield list.add(yield promiseNewDownload());
121
122 let items = yield list.getAll();
123 yield list.remove(items[0]);
124
125 // Removing an item that was never added should not raise an error.
126 yield list.remove(yield promiseNewDownload());
127
128 items = yield list.getAll();
129 do_check_eq(items.length, 1);
130 });
131
132 /**
133 * Tests that the "add", "remove", and "getAll" methods on the global
134 * DownloadCombinedList object combine the contents of the global DownloadList
135 * objects for public and private downloads.
136 */
137 add_task(function test_DownloadCombinedList_add_remove_getAll()
138 {
139 let publicList = yield promiseNewList();
140 let privateList = yield Downloads.getList(Downloads.PRIVATE);
141 let combinedList = yield Downloads.getList(Downloads.ALL);
142
143 let publicDownload = yield promiseNewDownload();
144 let privateDownload = yield Downloads.createDownload({
145 source: { url: httpUrl("source.txt"), isPrivate: true },
146 target: getTempFile(TEST_TARGET_FILE_NAME).path,
147 });
148
149 yield publicList.add(publicDownload);
150 yield privateList.add(privateDownload);
151
152 do_check_eq((yield combinedList.getAll()).length, 2);
153
154 yield combinedList.remove(publicDownload);
155 yield combinedList.remove(privateDownload);
156
157 do_check_eq((yield combinedList.getAll()).length, 0);
158
159 yield combinedList.add(publicDownload);
160 yield combinedList.add(privateDownload);
161
162 do_check_eq((yield publicList.getAll()).length, 1);
163 do_check_eq((yield privateList.getAll()).length, 1);
164 do_check_eq((yield combinedList.getAll()).length, 2);
165
166 yield publicList.remove(publicDownload);
167 yield privateList.remove(privateDownload);
168
169 do_check_eq((yield combinedList.getAll()).length, 0);
170 });
171
172 /**
173 * Checks that views receive the download add and remove notifications, and that
174 * adding and removing views works as expected, both for a normal and a combined
175 * list.
176 */
177 add_task(function test_notifications_add_remove()
178 {
179 for (let isCombined of [false, true]) {
180 // Force creating a new list for both the public and combined cases.
181 let list = yield promiseNewList();
182 if (isCombined) {
183 list = yield Downloads.getList(Downloads.ALL);
184 }
185
186 let downloadOne = yield promiseNewDownload();
187 let downloadTwo = yield Downloads.createDownload({
188 source: { url: httpUrl("source.txt"), isPrivate: true },
189 target: getTempFile(TEST_TARGET_FILE_NAME).path,
190 });
191 yield list.add(downloadOne);
192 yield list.add(downloadTwo);
193
194 // Check that we receive add notifications for existing elements.
195 let addNotifications = 0;
196 let viewOne = {
197 onDownloadAdded: function (aDownload) {
198 // The first download to be notified should be the first that was added.
199 if (addNotifications == 0) {
200 do_check_eq(aDownload, downloadOne);
201 } else if (addNotifications == 1) {
202 do_check_eq(aDownload, downloadTwo);
203 }
204 addNotifications++;
205 },
206 };
207 yield list.addView(viewOne);
208 do_check_eq(addNotifications, 2);
209
210 // Check that we receive add notifications for new elements.
211 yield list.add(yield promiseNewDownload());
212 do_check_eq(addNotifications, 3);
213
214 // Check that we receive remove notifications.
215 let removeNotifications = 0;
216 let viewTwo = {
217 onDownloadRemoved: function (aDownload) {
218 do_check_eq(aDownload, downloadOne);
219 removeNotifications++;
220 },
221 };
222 yield list.addView(viewTwo);
223 yield list.remove(downloadOne);
224 do_check_eq(removeNotifications, 1);
225
226 // We should not receive remove notifications after the view is removed.
227 yield list.removeView(viewTwo);
228 yield list.remove(downloadTwo);
229 do_check_eq(removeNotifications, 1);
230
231 // We should not receive add notifications after the view is removed.
232 yield list.removeView(viewOne);
233 yield list.add(yield promiseNewDownload());
234 do_check_eq(addNotifications, 3);
235 }
236 });
237
238 /**
239 * Checks that views receive the download change notifications, both for a
240 * normal and a combined list.
241 */
242 add_task(function test_notifications_change()
243 {
244 for (let isCombined of [false, true]) {
245 // Force creating a new list for both the public and combined cases.
246 let list = yield promiseNewList();
247 if (isCombined) {
248 list = yield Downloads.getList(Downloads.ALL);
249 }
250
251 let downloadOne = yield promiseNewDownload();
252 let downloadTwo = yield Downloads.createDownload({
253 source: { url: httpUrl("source.txt"), isPrivate: true },
254 target: getTempFile(TEST_TARGET_FILE_NAME).path,
255 });
256 yield list.add(downloadOne);
257 yield list.add(downloadTwo);
258
259 // Check that we receive change notifications.
260 let receivedOnDownloadChanged = false;
261 yield list.addView({
262 onDownloadChanged: function (aDownload) {
263 do_check_eq(aDownload, downloadOne);
264 receivedOnDownloadChanged = true;
265 },
266 });
267 yield downloadOne.start();
268 do_check_true(receivedOnDownloadChanged);
269
270 // We should not receive change notifications after a download is removed.
271 receivedOnDownloadChanged = false;
272 yield list.remove(downloadTwo);
273 yield downloadTwo.start();
274 do_check_false(receivedOnDownloadChanged);
275 }
276 });
277
278 /**
279 * Checks that the reference to "this" is correct in the view callbacks.
280 */
281 add_task(function test_notifications_this()
282 {
283 let list = yield promiseNewList();
284
285 // Check that we receive change notifications.
286 let receivedOnDownloadAdded = false;
287 let receivedOnDownloadChanged = false;
288 let receivedOnDownloadRemoved = false;
289 let view = {
290 onDownloadAdded: function () {
291 do_check_eq(this, view);
292 receivedOnDownloadAdded = true;
293 },
294 onDownloadChanged: function () {
295 // Only do this check once.
296 if (!receivedOnDownloadChanged) {
297 do_check_eq(this, view);
298 receivedOnDownloadChanged = true;
299 }
300 },
301 onDownloadRemoved: function () {
302 do_check_eq(this, view);
303 receivedOnDownloadRemoved = true;
304 },
305 };
306 yield list.addView(view);
307
308 let download = yield promiseNewDownload();
309 yield list.add(download);
310 yield download.start();
311 yield list.remove(download);
312
313 // Verify that we executed the checks.
314 do_check_true(receivedOnDownloadAdded);
315 do_check_true(receivedOnDownloadChanged);
316 do_check_true(receivedOnDownloadRemoved);
317 });
318
319 /**
320 * Checks that download is removed on history expiration.
321 */
322 add_task(function test_history_expiration()
323 {
324 mustInterruptResponses();
325
326 function cleanup() {
327 Services.prefs.clearUserPref("places.history.expiration.max_pages");
328 }
329 do_register_cleanup(cleanup);
330
331 // Set max pages to 0 to make the download expire.
332 Services.prefs.setIntPref("places.history.expiration.max_pages", 0);
333
334 let list = yield promiseNewList();
335 let downloadOne = yield promiseNewDownload();
336 let downloadTwo = yield promiseNewDownload(httpUrl("interruptible.txt"));
337
338 let deferred = Promise.defer();
339 let removeNotifications = 0;
340 let downloadView = {
341 onDownloadRemoved: function (aDownload) {
342 if (++removeNotifications == 2) {
343 deferred.resolve();
344 }
345 },
346 };
347 yield list.addView(downloadView);
348
349 // Work with one finished download and one canceled download.
350 yield downloadOne.start();
351 downloadTwo.start();
352 yield downloadTwo.cancel();
353
354 // We must replace the visits added while executing the downloads with visits
355 // that are older than 7 days, otherwise they will not be expired.
356 yield promiseClearHistory();
357 yield promiseExpirableDownloadVisit();
358 yield promiseExpirableDownloadVisit(httpUrl("interruptible.txt"));
359
360 // After clearing history, we can add the downloads to be removed to the list.
361 yield list.add(downloadOne);
362 yield list.add(downloadTwo);
363
364 // Force a history expiration.
365 Cc["@mozilla.org/places/expiration;1"]
366 .getService(Ci.nsIObserver).observe(null, "places-debug-start-expiration", -1);
367
368 // Wait for both downloads to be removed.
369 yield deferred.promise;
370
371 cleanup();
372 });
373
374 /**
375 * Checks all downloads are removed after clearing history.
376 */
377 add_task(function test_history_clear()
378 {
379 let list = yield promiseNewList();
380 let downloadOne = yield promiseNewDownload();
381 let downloadTwo = yield promiseNewDownload();
382 yield list.add(downloadOne);
383 yield list.add(downloadTwo);
384
385 let deferred = Promise.defer();
386 let removeNotifications = 0;
387 let downloadView = {
388 onDownloadRemoved: function (aDownload) {
389 if (++removeNotifications == 2) {
390 deferred.resolve();
391 }
392 },
393 };
394 yield list.addView(downloadView);
395
396 yield downloadOne.start();
397 yield downloadTwo.start();
398
399 yield promiseClearHistory();
400
401 // Wait for the removal notifications that may still be pending.
402 yield deferred.promise;
403 });
404
405 /**
406 * Tests the removeFinished method to ensure that it only removes
407 * finished downloads.
408 */
409 add_task(function test_removeFinished()
410 {
411 let list = yield promiseNewList();
412 let downloadOne = yield promiseNewDownload();
413 let downloadTwo = yield promiseNewDownload();
414 let downloadThree = yield promiseNewDownload();
415 let downloadFour = yield promiseNewDownload();
416 yield list.add(downloadOne);
417 yield list.add(downloadTwo);
418 yield list.add(downloadThree);
419 yield list.add(downloadFour);
420
421 let deferred = Promise.defer();
422 let removeNotifications = 0;
423 let downloadView = {
424 onDownloadRemoved: function (aDownload) {
425 do_check_true(aDownload == downloadOne ||
426 aDownload == downloadTwo ||
427 aDownload == downloadThree);
428 do_check_true(removeNotifications < 3);
429 if (++removeNotifications == 3) {
430 deferred.resolve();
431 }
432 },
433 };
434 yield list.addView(downloadView);
435
436 // Start three of the downloads, but don't start downloadTwo, then set
437 // downloadFour to have partial data. All downloads except downloadFour
438 // should be removed.
439 yield downloadOne.start();
440 yield downloadThree.start();
441 yield downloadFour.start();
442 downloadFour.hasPartialData = true;
443
444 list.removeFinished();
445 yield deferred.promise;
446
447 let downloads = yield list.getAll()
448 do_check_eq(downloads.length, 1);
449 });
450
451 /**
452 * Tests the global DownloadSummary objects for the public, private, and
453 * combined download lists.
454 */
455 add_task(function test_DownloadSummary()
456 {
457 mustInterruptResponses();
458
459 let publicList = yield promiseNewList();
460 let privateList = yield Downloads.getList(Downloads.PRIVATE);
461
462 let publicSummary = yield Downloads.getSummary(Downloads.PUBLIC);
463 let privateSummary = yield Downloads.getSummary(Downloads.PRIVATE);
464 let combinedSummary = yield Downloads.getSummary(Downloads.ALL);
465
466 // Add a public download that has succeeded.
467 let succeededPublicDownload = yield promiseNewDownload();
468 yield succeededPublicDownload.start();
469 yield publicList.add(succeededPublicDownload);
470
471 // Add a public download that has been canceled midway.
472 let canceledPublicDownload =
473 yield promiseNewDownload(httpUrl("interruptible.txt"));
474 canceledPublicDownload.start();
475 yield promiseDownloadMidway(canceledPublicDownload);
476 yield canceledPublicDownload.cancel();
477 yield publicList.add(canceledPublicDownload);
478
479 // Add a public download that is in progress.
480 let inProgressPublicDownload =
481 yield promiseNewDownload(httpUrl("interruptible.txt"));
482 inProgressPublicDownload.start();
483 yield promiseDownloadMidway(inProgressPublicDownload);
484 yield publicList.add(inProgressPublicDownload);
485
486 // Add a private download that is in progress.
487 let inProgressPrivateDownload = yield Downloads.createDownload({
488 source: { url: httpUrl("interruptible.txt"), isPrivate: true },
489 target: getTempFile(TEST_TARGET_FILE_NAME).path,
490 });
491 inProgressPrivateDownload.start();
492 yield promiseDownloadMidway(inProgressPrivateDownload);
493 yield privateList.add(inProgressPrivateDownload);
494
495 // Verify that the summary includes the total number of bytes and the
496 // currently transferred bytes only for the downloads that are not stopped.
497 // For simplicity, we assume that after a download is added to the list, its
498 // current state is immediately propagated to the summary object, which is
499 // true in the current implementation, though it is not guaranteed as all the
500 // download operations may happen asynchronously.
501 do_check_false(publicSummary.allHaveStopped);
502 do_check_eq(publicSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
503 do_check_eq(publicSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
504
505 do_check_false(privateSummary.allHaveStopped);
506 do_check_eq(privateSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
507 do_check_eq(privateSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
508
509 do_check_false(combinedSummary.allHaveStopped);
510 do_check_eq(combinedSummary.progressTotalBytes, TEST_DATA_SHORT.length * 4);
511 do_check_eq(combinedSummary.progressCurrentBytes, TEST_DATA_SHORT.length * 2);
512
513 yield inProgressPublicDownload.cancel();
514
515 // Stopping the download should have excluded it from the summary.
516 do_check_true(publicSummary.allHaveStopped);
517 do_check_eq(publicSummary.progressTotalBytes, 0);
518 do_check_eq(publicSummary.progressCurrentBytes, 0);
519
520 do_check_false(privateSummary.allHaveStopped);
521 do_check_eq(privateSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
522 do_check_eq(privateSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
523
524 do_check_false(combinedSummary.allHaveStopped);
525 do_check_eq(combinedSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
526 do_check_eq(combinedSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
527
528 yield inProgressPrivateDownload.cancel();
529
530 // All the downloads should be stopped now.
531 do_check_true(publicSummary.allHaveStopped);
532 do_check_eq(publicSummary.progressTotalBytes, 0);
533 do_check_eq(publicSummary.progressCurrentBytes, 0);
534
535 do_check_true(privateSummary.allHaveStopped);
536 do_check_eq(privateSummary.progressTotalBytes, 0);
537 do_check_eq(privateSummary.progressCurrentBytes, 0);
538
539 do_check_true(combinedSummary.allHaveStopped);
540 do_check_eq(combinedSummary.progressTotalBytes, 0);
541 do_check_eq(combinedSummary.progressCurrentBytes, 0);
542 });
543
544 /**
545 * Checks that views receive the summary change notification. This is tested on
546 * the combined summary when adding a public download, as we assume that if we
547 * pass the test in this case we will also pass it in the others.
548 */
549 add_task(function test_DownloadSummary_notifications()
550 {
551 let list = yield promiseNewList();
552 let summary = yield Downloads.getSummary(Downloads.ALL);
553
554 let download = yield promiseNewDownload();
555 yield list.add(download);
556
557 // Check that we receive change notifications.
558 let receivedOnSummaryChanged = false;
559 yield summary.addView({
560 onSummaryChanged: function () {
561 receivedOnSummaryChanged = true;
562 },
563 });
564 yield download.start();
565 do_check_true(receivedOnSummaryChanged);
566 });
567
568 ////////////////////////////////////////////////////////////////////////////////
569 //// Termination
570
571 let tailFile = do_get_file("tail.js");
572 Services.scriptloader.loadSubScript(NetUtil.newURI(tailFile).spec);

mercurial