Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { actionTypes as at } from "common/Actions.mjs";
import { DownloadsManager } from "lib/DownloadsManager.sys.mjs";
import { stubGlobals } from "test/jest/test-utils";
describe("Downloads Manager", () => {
let downloadsManager;
let restoreGlobals;
let downloadData;
let downloadsCommon;
let isBlocked;
const DOWNLOAD_URL = "https://site.com/download.mov";
beforeEach(() => {
downloadData = {
addView: jest.fn(),
removeView: jest.fn(),
};
downloadsCommon = {
getData: jest.fn(() => downloadData),
copyDownloadLink: jest.fn(),
deleteDownload: jest.fn(() => Promise.resolve()),
openDownload: jest.fn(),
showDownloadedFile: jest.fn(),
};
isBlocked = jest.fn(() => false);
restoreGlobals = stubGlobals({
Cc: {
"@mozilla.org/timer;1": {
createInstance() {
return {
initWithCallback: jest.fn(callback => callback()),
cancel: jest.fn(),
};
},
},
},
Ci: { nsITimer: { TYPE_ONE_SHOT: 1 } },
DownloadsCommon: downloadsCommon,
// getSizeWithUnits drives the formatted description asserted below.
DownloadsViewUI: {
getDisplayName: () => "filename.ext",
getSizeWithUnits: () => "1.5 MB",
},
FileUtils: { File: class {} },
BrowserUtils: {
whereToOpenLink: jest.fn(() => "current"),
},
NewTabUtils: { blockedLinks: { isBlocked } },
});
downloadsManager = new DownloadsManager();
downloadsManager.init({ dispatch() {} });
downloadsManager.onDownloadAdded({
source: { url: DOWNLOAD_URL },
endTime: Date.now(),
target: { path: "/path/to/download.mov", exists: true },
succeeded: true,
refresh: async () => {},
});
expect(downloadsManager._downloadItems.has(DOWNLOAD_URL)).toBe(true);
});
afterEach(() => {
downloadsManager._downloadItems.clear();
restoreGlobals();
});
describe("#init", () => {
it("should add a DownloadsCommon view on init", () => {
downloadsManager.init({ dispatch() {} });
expect(downloadData.addView).toHaveBeenCalledTimes(2);
});
});
describe("#onAction", () => {
it("should copy the file on COPY_DOWNLOAD_LINK", () => {
downloadsManager.onAction({
type: at.COPY_DOWNLOAD_LINK,
data: { url: DOWNLOAD_URL },
});
expect(downloadsCommon.copyDownloadLink).toHaveBeenCalledTimes(1);
});
it("should remove the file on REMOVE_DOWNLOAD_FILE", () => {
downloadsManager.onAction({
type: at.REMOVE_DOWNLOAD_FILE,
data: { url: DOWNLOAD_URL },
});
expect(downloadsCommon.deleteDownload).toHaveBeenCalledTimes(1);
});
it("should show the file on SHOW_DOWNLOAD_FILE", () => {
downloadsManager.onAction({
type: at.SHOW_DOWNLOAD_FILE,
data: { url: DOWNLOAD_URL },
});
expect(downloadsCommon.showDownloadedFile).toHaveBeenCalledTimes(1);
});
it("should open the file on OPEN_DOWNLOAD_FILE if the type is download", () => {
downloadsManager.onAction({
type: at.OPEN_DOWNLOAD_FILE,
data: { url: DOWNLOAD_URL, type: "download" },
_target: { browser: {} },
});
expect(downloadsCommon.openDownload).toHaveBeenCalledTimes(1);
});
it("should copy the file on UNINIT", () => {
// DownloadsManager._downloadData needs to exist first
downloadsManager.onAction({ type: at.UNINIT });
expect(downloadData.removeView).toHaveBeenCalledTimes(1);
});
it("should not execute a download command if we do not have the correct url", () => {
downloadsManager.onAction({
type: at.SHOW_DOWNLOAD_FILE,
data: { url: "unknown_url" },
});
expect(downloadsCommon.showDownloadedFile).not.toHaveBeenCalled();
});
});
describe("#onDownloadAdded", () => {
let newDownload;
beforeEach(() => {
downloadsManager._downloadItems.clear();
newDownload = {
source: { url: "https://site.com/newDownload.mov" },
endTime: Date.now(),
target: { path: "/path/to/newDownload.mov", exists: true },
succeeded: true,
refresh: async () => {},
};
});
afterEach(() => {
downloadsManager._downloadItems.clear();
});
it("should add a download on onDownloadAdded", () => {
downloadsManager.onDownloadAdded(newDownload);
expect(
downloadsManager._downloadItems.has("https://site.com/newDownload.mov")
).toBe(true);
});
it("should not add a download if it already exists", () => {
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(newDownload);
const results = downloadsManager._downloadItems;
expect(results.size).toBe(1);
});
it("should not return any downloads if no threshold is provided", async () => {
downloadsManager.onDownloadAdded(newDownload);
const results = await downloadsManager.getDownloads(null, {});
expect(results).toHaveLength(0);
});
it("should stop at numItems when it found one it's looking for", async () => {
const aDownload = {
source: { url: "https://site.com/aDownload.pdf" },
endTime: Date.now(),
target: { path: "/path/to/aDownload.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(aDownload);
downloadsManager.onDownloadAdded(newDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 1,
onlySucceeded: true,
onlyExists: true,
});
expect(results).toHaveLength(1);
expect(results[0].url).toBe(aDownload.source.url);
});
it("should get all the downloads younger than the threshold provided", async () => {
const oldDownload = {
source: { url: "https://site.com/oldDownload.pdf" },
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/oldDownload.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
// Add an old download (older than 36 hours in this case)
downloadsManager.onDownloadAdded(oldDownload);
downloadsManager.onDownloadAdded(newDownload);
const RECENT_DOWNLOAD_THRESHOLD = 36 * 60 * 60 * 1000;
const results = await downloadsManager.getDownloads(
RECENT_DOWNLOAD_THRESHOLD,
{ numItems: 5, onlySucceeded: true, onlyExists: true }
);
expect(results).toHaveLength(1);
expect(results[0].url).toBe(newDownload.source.url);
});
it("should dispatch DOWNLOAD_CHANGED when adding a download", () => {
downloadsManager._store.dispatch = jest.fn();
downloadsManager._downloadTimer = null; // Nuke the timer
downloadsManager.onDownloadAdded(newDownload);
expect(downloadsManager._store.dispatch).toHaveBeenCalledTimes(1);
});
it("should refresh the downloads if onlyExists is true", async () => {
const aDownload = {
source: { url: "https://site.com/aDownload.pdf" },
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/aDownload.pdf", exists: true },
succeeded: true,
refresh: () => {},
};
jest.spyOn(aDownload, "refresh").mockResolvedValue(undefined);
downloadsManager.onDownloadAdded(aDownload);
await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
onlyExists: true,
});
expect(aDownload.refresh).toHaveBeenCalledTimes(1);
});
it("should not refresh the downloads if onlyExists is false (by default)", async () => {
const aDownload = {
source: { url: "https://site.com/aDownload.pdf" },
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/aDownload.pdf", exists: true },
succeeded: true,
refresh: () => {},
};
jest.spyOn(aDownload, "refresh").mockResolvedValue(undefined);
downloadsManager.onDownloadAdded(aDownload);
await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
});
expect(aDownload.refresh).not.toHaveBeenCalled();
});
it("should only return downloads that exist if specified", async () => {
const nonExistantDownload = {
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/nonExistantDownload.pdf", exists: false },
succeeded: true,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(nonExistantDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
onlyExists: true,
});
expect(results).toHaveLength(1);
expect(results[0].url).toBe(newDownload.source.url);
});
it("should return all downloads that either exist or don't exist if not specified", async () => {
const nonExistantDownload = {
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/nonExistantDownload.pdf", exists: false },
succeeded: true,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(nonExistantDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
});
expect(results).toHaveLength(2);
expect(results[0].url).toBe(newDownload.source.url);
expect(results[1].url).toBe(nonExistantDownload.source.url);
});
it("should return only unblocked downloads", async () => {
const nonExistantDownload = {
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/nonExistantDownload.pdf", exists: false },
succeeded: true,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(nonExistantDownload);
isBlocked.mockImplementation(
item => item.url === nonExistantDownload.source.url
);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
});
expect(results).toHaveLength(1);
expect(results[0]).toHaveProperty("url", newDownload.source.url);
});
it("should only return downloads that were successful if specified", async () => {
const nonSuccessfulDownload = {
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/nonSuccessfulDownload.pdf", exists: false },
succeeded: false,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(nonSuccessfulDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
});
expect(results).toHaveLength(1);
expect(results[0].url).toBe(newDownload.source.url);
});
it("should return all downloads that were either successful or not if not specified", async () => {
const nonExistantDownload = {
endTime: Date.now() - 40 * 60 * 60 * 1000,
target: { path: "/path/to/nonExistantDownload.pdf", exists: true },
succeeded: false,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(nonExistantDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
});
expect(results).toHaveLength(2);
expect(results[0].url).toBe(newDownload.source.url);
expect(results[1].url).toBe(nonExistantDownload.source.url);
});
it("should sort the downloads by recency", async () => {
const olderDownload1 = {
source: { url: "https://site.com/oldDownload1.pdf" },
endTime: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago
target: { path: "/path/to/oldDownload1.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
const olderDownload2 = {
source: { url: "https://site.com/oldDownload2.pdf" },
endTime: Date.now() - 60 * 60 * 1000, // 1 hour ago
target: { path: "/path/to/oldDownload2.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
// Add some older downloads and check that they are in order
downloadsManager.onDownloadAdded(olderDownload1);
downloadsManager.onDownloadAdded(olderDownload2);
downloadsManager.onDownloadAdded(newDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
onlyExists: true,
});
expect(results).toHaveLength(3);
expect(results[0].url).toBe(newDownload.source.url);
expect(results[1].url).toBe(olderDownload2.source.url);
expect(results[2].url).toBe(olderDownload1.source.url);
});
it("should sort downloads by recency regardless of insertion order", async () => {
const download1 = {
source: { url: "https://site.com/download1.pdf" },
endTime: Date.now() - 3 * 60 * 60 * 1000, // 3 hours ago
target: { path: "/path/to/download1.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
const download2 = {
source: { url: "https://site.com/download2.pdf" },
endTime: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago
target: { path: "/path/to/download2.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
const download3 = {
source: { url: "https://site.com/download3.pdf" },
endTime: Date.now() - 60 * 60 * 1000, // 1 hour ago
target: { path: "/path/to/download3.pdf", exists: true },
succeeded: true,
refresh: async () => {},
};
// Add newest first, then oldest, then middle — insertion order
// differs from expected recency order
downloadsManager.onDownloadAdded(newDownload);
downloadsManager.onDownloadAdded(download1);
downloadsManager.onDownloadAdded(download3);
downloadsManager.onDownloadAdded(download2);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
onlyExists: true,
});
expect(results).toHaveLength(4);
expect(results[0].url).toBe(newDownload.source.url);
expect(results[1].url).toBe(download3.source.url);
expect(results[2].url).toBe(download2.source.url);
expect(results[3].url).toBe(download1.source.url);
});
it("should format the description properly if there is no file type", async () => {
newDownload.target.path = null;
downloadsManager.onDownloadAdded(newDownload);
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
onlySucceeded: true,
onlyExists: true,
});
expect(results).toHaveLength(1);
// see the DownloadsViewUI stub above to see where this comes from
expect(results[0].description).toBe("1.5 MB");
});
});
describe("#onDownloadRemoved", () => {
let newDownload;
beforeEach(() => {
downloadsManager._downloadItems.clear();
newDownload = {
source: { url: "https://site.com/removeMe.mov" },
endTime: Date.now(),
target: { path: "/path/to/removeMe.mov", exists: true },
succeeded: true,
refresh: async () => {},
};
downloadsManager.onDownloadAdded(newDownload);
});
it("should remove a download if it exists on onDownloadRemoved", async () => {
downloadsManager.onDownloadRemoved({
source: { url: "https://site.com/removeMe.mov" },
});
const results = await downloadsManager.getDownloads(Infinity, {
numItems: 5,
});
expect(results).toEqual([]);
});
it("should dispatch DOWNLOAD_CHANGED when removing a download", () => {
downloadsManager._store.dispatch = jest.fn();
downloadsManager.onDownloadRemoved({
source: { url: "https://site.com/removeMe.mov" },
});
expect(downloadsManager._store.dispatch).toHaveBeenCalledTimes(1);
});
});
});