gecko-dev/toolkit/components/jsdownloads/test/unit/test_DownloadList.js

573 строки
18 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the DownloadList object.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
/**
* Returns a PRTime in the past usable to add expirable visits.
*
* @note Expiration ignores any visit added in the last 7 days, but it's
* better be safe against DST issues, by going back one day more.
*/
function getExpirablePRTime()
{
let dateObj = new Date();
// Normalize to midnight
dateObj.setHours(0);
dateObj.setMinutes(0);
dateObj.setSeconds(0);
dateObj.setMilliseconds(0);
dateObj = new Date(dateObj.getTime() - 8 * 86400000);
return dateObj.getTime() * 1000;
}
/**
* Adds an expirable history visit for a download.
*
* @param aSourceUrl
* String containing the URI for the download source, or null to use
* httpUrl("source.txt").
*
* @return {Promise}
* @rejects JavaScript exception.
*/
function promiseExpirableDownloadVisit(aSourceUrl)
{
let deferred = Promise.defer();
PlacesUtils.asyncHistory.updatePlaces(
{
uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")),
visits: [{
transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
visitDate: getExpirablePRTime(),
}]
},
{
handleError: function handleError(aResultCode, aPlaceInfo) {
let ex = new Components.Exception("Unexpected error in adding visits.",
aResultCode);
deferred.reject(ex);
},
handleResult: function () {},
handleCompletion: function handleCompletion() {
deferred.resolve();
}
});
return deferred.promise;
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
/**
* Checks the testing mechanism used to build different download lists.
*/
add_task(function test_construction()
{
let downloadListOne = yield promiseNewList();
let downloadListTwo = yield promiseNewList();
let privateDownloadListOne = yield promiseNewList(true);
let privateDownloadListTwo = yield promiseNewList(true);
do_check_neq(downloadListOne, downloadListTwo);
do_check_neq(privateDownloadListOne, privateDownloadListTwo);
do_check_neq(downloadListOne, privateDownloadListOne);
});
/**
* Checks the methods to add and retrieve items from the list.
*/
add_task(function test_add_getAll()
{
let list = yield promiseNewList();
let downloadOne = yield promiseNewDownload();
yield list.add(downloadOne);
let itemsOne = yield list.getAll();
do_check_eq(itemsOne.length, 1);
do_check_eq(itemsOne[0], downloadOne);
let downloadTwo = yield promiseNewDownload();
yield list.add(downloadTwo);
let itemsTwo = yield list.getAll();
do_check_eq(itemsTwo.length, 2);
do_check_eq(itemsTwo[0], downloadOne);
do_check_eq(itemsTwo[1], downloadTwo);
// The first snapshot should not have been modified.
do_check_eq(itemsOne.length, 1);
});
/**
* Checks the method to remove items from the list.
*/
add_task(function test_remove()
{
let list = yield promiseNewList();
yield list.add(yield promiseNewDownload());
yield list.add(yield promiseNewDownload());
let items = yield list.getAll();
yield list.remove(items[0]);
// Removing an item that was never added should not raise an error.
yield list.remove(yield promiseNewDownload());
items = yield list.getAll();
do_check_eq(items.length, 1);
});
/**
* Tests that the "add", "remove", and "getAll" methods on the global
* DownloadCombinedList object combine the contents of the global DownloadList
* objects for public and private downloads.
*/
add_task(function test_DownloadCombinedList_add_remove_getAll()
{
let publicList = yield promiseNewList();
let privateList = yield Downloads.getList(Downloads.PRIVATE);
let combinedList = yield Downloads.getList(Downloads.ALL);
let publicDownload = yield promiseNewDownload();
let privateDownload = yield Downloads.createDownload({
source: { url: httpUrl("source.txt"), isPrivate: true },
target: getTempFile(TEST_TARGET_FILE_NAME).path,
});
yield publicList.add(publicDownload);
yield privateList.add(privateDownload);
do_check_eq((yield combinedList.getAll()).length, 2);
yield combinedList.remove(publicDownload);
yield combinedList.remove(privateDownload);
do_check_eq((yield combinedList.getAll()).length, 0);
yield combinedList.add(publicDownload);
yield combinedList.add(privateDownload);
do_check_eq((yield publicList.getAll()).length, 1);
do_check_eq((yield privateList.getAll()).length, 1);
do_check_eq((yield combinedList.getAll()).length, 2);
yield publicList.remove(publicDownload);
yield privateList.remove(privateDownload);
do_check_eq((yield combinedList.getAll()).length, 0);
});
/**
* Checks that views receive the download add and remove notifications, and that
* adding and removing views works as expected, both for a normal and a combined
* list.
*/
add_task(function test_notifications_add_remove()
{
for (let isCombined of [false, true]) {
// Force creating a new list for both the public and combined cases.
let list = yield promiseNewList();
if (isCombined) {
list = yield Downloads.getList(Downloads.ALL);
}
let downloadOne = yield promiseNewDownload();
let downloadTwo = yield Downloads.createDownload({
source: { url: httpUrl("source.txt"), isPrivate: true },
target: getTempFile(TEST_TARGET_FILE_NAME).path,
});
yield list.add(downloadOne);
yield list.add(downloadTwo);
// Check that we receive add notifications for existing elements.
let addNotifications = 0;
let viewOne = {
onDownloadAdded: function (aDownload) {
// The first download to be notified should be the first that was added.
if (addNotifications == 0) {
do_check_eq(aDownload, downloadOne);
} else if (addNotifications == 1) {
do_check_eq(aDownload, downloadTwo);
}
addNotifications++;
},
};
yield list.addView(viewOne);
do_check_eq(addNotifications, 2);
// Check that we receive add notifications for new elements.
yield list.add(yield promiseNewDownload());
do_check_eq(addNotifications, 3);
// Check that we receive remove notifications.
let removeNotifications = 0;
let viewTwo = {
onDownloadRemoved: function (aDownload) {
do_check_eq(aDownload, downloadOne);
removeNotifications++;
},
};
yield list.addView(viewTwo);
yield list.remove(downloadOne);
do_check_eq(removeNotifications, 1);
// We should not receive remove notifications after the view is removed.
yield list.removeView(viewTwo);
yield list.remove(downloadTwo);
do_check_eq(removeNotifications, 1);
// We should not receive add notifications after the view is removed.
yield list.removeView(viewOne);
yield list.add(yield promiseNewDownload());
do_check_eq(addNotifications, 3);
}
});
/**
* Checks that views receive the download change notifications, both for a
* normal and a combined list.
*/
add_task(function test_notifications_change()
{
for (let isCombined of [false, true]) {
// Force creating a new list for both the public and combined cases.
let list = yield promiseNewList();
if (isCombined) {
list = yield Downloads.getList(Downloads.ALL);
}
let downloadOne = yield promiseNewDownload();
let downloadTwo = yield Downloads.createDownload({
source: { url: httpUrl("source.txt"), isPrivate: true },
target: getTempFile(TEST_TARGET_FILE_NAME).path,
});
yield list.add(downloadOne);
yield list.add(downloadTwo);
// Check that we receive change notifications.
let receivedOnDownloadChanged = false;
yield list.addView({
onDownloadChanged: function (aDownload) {
do_check_eq(aDownload, downloadOne);
receivedOnDownloadChanged = true;
},
});
yield downloadOne.start();
do_check_true(receivedOnDownloadChanged);
// We should not receive change notifications after a download is removed.
receivedOnDownloadChanged = false;
yield list.remove(downloadTwo);
yield downloadTwo.start();
do_check_false(receivedOnDownloadChanged);
}
});
/**
* Checks that the reference to "this" is correct in the view callbacks.
*/
add_task(function test_notifications_this()
{
let list = yield promiseNewList();
// Check that we receive change notifications.
let receivedOnDownloadAdded = false;
let receivedOnDownloadChanged = false;
let receivedOnDownloadRemoved = false;
let view = {
onDownloadAdded: function () {
do_check_eq(this, view);
receivedOnDownloadAdded = true;
},
onDownloadChanged: function () {
// Only do this check once.
if (!receivedOnDownloadChanged) {
do_check_eq(this, view);
receivedOnDownloadChanged = true;
}
},
onDownloadRemoved: function () {
do_check_eq(this, view);
receivedOnDownloadRemoved = true;
},
};
yield list.addView(view);
let download = yield promiseNewDownload();
yield list.add(download);
yield download.start();
yield list.remove(download);
// Verify that we executed the checks.
do_check_true(receivedOnDownloadAdded);
do_check_true(receivedOnDownloadChanged);
do_check_true(receivedOnDownloadRemoved);
});
/**
* Checks that download is removed on history expiration.
*/
add_task(function test_history_expiration()
{
mustInterruptResponses();
function cleanup() {
Services.prefs.clearUserPref("places.history.expiration.max_pages");
}
do_register_cleanup(cleanup);
// Set max pages to 0 to make the download expire.
Services.prefs.setIntPref("places.history.expiration.max_pages", 0);
let list = yield promiseNewList();
let downloadOne = yield promiseNewDownload();
let downloadTwo = yield promiseNewDownload(httpUrl("interruptible.txt"));
let deferred = Promise.defer();
let removeNotifications = 0;
let downloadView = {
onDownloadRemoved: function (aDownload) {
if (++removeNotifications == 2) {
deferred.resolve();
}
},
};
yield list.addView(downloadView);
// Work with one finished download and one canceled download.
yield downloadOne.start();
downloadTwo.start();
yield downloadTwo.cancel();
// We must replace the visits added while executing the downloads with visits
// that are older than 7 days, otherwise they will not be expired.
yield PlacesTestUtils.clearHistory();
yield promiseExpirableDownloadVisit();
yield promiseExpirableDownloadVisit(httpUrl("interruptible.txt"));
// After clearing history, we can add the downloads to be removed to the list.
yield list.add(downloadOne);
yield list.add(downloadTwo);
// Force a history expiration.
Cc["@mozilla.org/places/expiration;1"]
.getService(Ci.nsIObserver).observe(null, "places-debug-start-expiration", -1);
// Wait for both downloads to be removed.
yield deferred.promise;
cleanup();
});
/**
* Checks all downloads are removed after clearing history.
*/
add_task(function test_history_clear()
{
let list = yield promiseNewList();
let downloadOne = yield promiseNewDownload();
let downloadTwo = yield promiseNewDownload();
yield list.add(downloadOne);
yield list.add(downloadTwo);
let deferred = Promise.defer();
let removeNotifications = 0;
let downloadView = {
onDownloadRemoved: function (aDownload) {
if (++removeNotifications == 2) {
deferred.resolve();
}
},
};
yield list.addView(downloadView);
yield downloadOne.start();
yield downloadTwo.start();
yield PlacesTestUtils.clearHistory();
// Wait for the removal notifications that may still be pending.
yield deferred.promise;
});
/**
* Tests the removeFinished method to ensure that it only removes
* finished downloads.
*/
add_task(function test_removeFinished()
{
let list = yield promiseNewList();
let downloadOne = yield promiseNewDownload();
let downloadTwo = yield promiseNewDownload();
let downloadThree = yield promiseNewDownload();
let downloadFour = yield promiseNewDownload();
yield list.add(downloadOne);
yield list.add(downloadTwo);
yield list.add(downloadThree);
yield list.add(downloadFour);
let deferred = Promise.defer();
let removeNotifications = 0;
let downloadView = {
onDownloadRemoved: function (aDownload) {
do_check_true(aDownload == downloadOne ||
aDownload == downloadTwo ||
aDownload == downloadThree);
do_check_true(removeNotifications < 3);
if (++removeNotifications == 3) {
deferred.resolve();
}
},
};
yield list.addView(downloadView);
// Start three of the downloads, but don't start downloadTwo, then set
// downloadFour to have partial data. All downloads except downloadFour
// should be removed.
yield downloadOne.start();
yield downloadThree.start();
yield downloadFour.start();
downloadFour.hasPartialData = true;
list.removeFinished();
yield deferred.promise;
let downloads = yield list.getAll()
do_check_eq(downloads.length, 1);
});
/**
* Tests the global DownloadSummary objects for the public, private, and
* combined download lists.
*/
add_task(function test_DownloadSummary()
{
mustInterruptResponses();
let publicList = yield promiseNewList();
let privateList = yield Downloads.getList(Downloads.PRIVATE);
let publicSummary = yield Downloads.getSummary(Downloads.PUBLIC);
let privateSummary = yield Downloads.getSummary(Downloads.PRIVATE);
let combinedSummary = yield Downloads.getSummary(Downloads.ALL);
// Add a public download that has succeeded.
let succeededPublicDownload = yield promiseNewDownload();
yield succeededPublicDownload.start();
yield publicList.add(succeededPublicDownload);
// Add a public download that has been canceled midway.
let canceledPublicDownload =
yield promiseNewDownload(httpUrl("interruptible.txt"));
canceledPublicDownload.start();
yield promiseDownloadMidway(canceledPublicDownload);
yield canceledPublicDownload.cancel();
yield publicList.add(canceledPublicDownload);
// Add a public download that is in progress.
let inProgressPublicDownload =
yield promiseNewDownload(httpUrl("interruptible.txt"));
inProgressPublicDownload.start();
yield promiseDownloadMidway(inProgressPublicDownload);
yield publicList.add(inProgressPublicDownload);
// Add a private download that is in progress.
let inProgressPrivateDownload = yield Downloads.createDownload({
source: { url: httpUrl("interruptible.txt"), isPrivate: true },
target: getTempFile(TEST_TARGET_FILE_NAME).path,
});
inProgressPrivateDownload.start();
yield promiseDownloadMidway(inProgressPrivateDownload);
yield privateList.add(inProgressPrivateDownload);
// Verify that the summary includes the total number of bytes and the
// currently transferred bytes only for the downloads that are not stopped.
// For simplicity, we assume that after a download is added to the list, its
// current state is immediately propagated to the summary object, which is
// true in the current implementation, though it is not guaranteed as all the
// download operations may happen asynchronously.
do_check_false(publicSummary.allHaveStopped);
do_check_eq(publicSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
do_check_eq(publicSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
do_check_false(privateSummary.allHaveStopped);
do_check_eq(privateSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
do_check_eq(privateSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
do_check_false(combinedSummary.allHaveStopped);
do_check_eq(combinedSummary.progressTotalBytes, TEST_DATA_SHORT.length * 4);
do_check_eq(combinedSummary.progressCurrentBytes, TEST_DATA_SHORT.length * 2);
yield inProgressPublicDownload.cancel();
// Stopping the download should have excluded it from the summary.
do_check_true(publicSummary.allHaveStopped);
do_check_eq(publicSummary.progressTotalBytes, 0);
do_check_eq(publicSummary.progressCurrentBytes, 0);
do_check_false(privateSummary.allHaveStopped);
do_check_eq(privateSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
do_check_eq(privateSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
do_check_false(combinedSummary.allHaveStopped);
do_check_eq(combinedSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2);
do_check_eq(combinedSummary.progressCurrentBytes, TEST_DATA_SHORT.length);
yield inProgressPrivateDownload.cancel();
// All the downloads should be stopped now.
do_check_true(publicSummary.allHaveStopped);
do_check_eq(publicSummary.progressTotalBytes, 0);
do_check_eq(publicSummary.progressCurrentBytes, 0);
do_check_true(privateSummary.allHaveStopped);
do_check_eq(privateSummary.progressTotalBytes, 0);
do_check_eq(privateSummary.progressCurrentBytes, 0);
do_check_true(combinedSummary.allHaveStopped);
do_check_eq(combinedSummary.progressTotalBytes, 0);
do_check_eq(combinedSummary.progressCurrentBytes, 0);
});
/**
* Checks that views receive the summary change notification. This is tested on
* the combined summary when adding a public download, as we assume that if we
* pass the test in this case we will also pass it in the others.
*/
add_task(function test_DownloadSummary_notifications()
{
let list = yield promiseNewList();
let summary = yield Downloads.getSummary(Downloads.ALL);
let download = yield promiseNewDownload();
yield list.add(download);
// Check that we receive change notifications.
let receivedOnSummaryChanged = false;
yield summary.addView({
onSummaryChanged: function () {
receivedOnSummaryChanged = true;
},
});
yield download.start();
do_check_true(receivedOnSummaryChanged);
});
////////////////////////////////////////////////////////////////////////////////
//// Termination
let tailFile = do_get_file("tail.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(tailFile).spec);