import { actionTypes as at } from "common/Actions.sys.mjs"; import { DownloadsManager } from "lib/DownloadsManager.jsm"; import { GlobalOverrider } from "test/unit/utils"; describe("Downloads Manager", () => { let downloadsManager; let globals; const DOWNLOAD_URL = "https://site.com/download.mov"; beforeEach(() => { globals = new GlobalOverrider(); global.Cc["@mozilla.org/timer;1"] = { createInstance() { return { initWithCallback: sinon.stub().callsFake(callback => callback()), cancel: sinon.spy(), }; }, }; globals.set("DownloadsCommon", { getData: sinon.stub().returns({ addView: sinon.stub(), removeView: sinon.stub(), }), copyDownloadLink: sinon.stub(), deleteDownload: sinon.stub().returns(Promise.resolve()), openDownload: sinon.stub(), showDownloadedFile: sinon.stub(), }); 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 () => {}, }); assert.ok(downloadsManager._downloadItems.has(DOWNLOAD_URL)); globals.set("NewTabUtils", { blockedLinks: { isBlocked() {} } }); }); afterEach(() => { downloadsManager._downloadItems.clear(); globals.restore(); }); describe("#init", () => { it("should add a DownloadsCommon view on init", () => { downloadsManager.init({ dispatch() {} }); assert.calledTwice(global.DownloadsCommon.getData().addView); }); }); describe("#onAction", () => { it("should copy the file on COPY_DOWNLOAD_LINK", () => { downloadsManager.onAction({ type: at.COPY_DOWNLOAD_LINK, data: { url: DOWNLOAD_URL }, }); assert.calledOnce(global.DownloadsCommon.copyDownloadLink); }); it("should remove the file on REMOVE_DOWNLOAD_FILE", () => { downloadsManager.onAction({ type: at.REMOVE_DOWNLOAD_FILE, data: { url: DOWNLOAD_URL }, }); assert.calledOnce(global.DownloadsCommon.deleteDownload); }); it("should show the file on SHOW_DOWNLOAD_FILE", () => { downloadsManager.onAction({ type: at.SHOW_DOWNLOAD_FILE, data: { url: DOWNLOAD_URL }, }); assert.calledOnce(global.DownloadsCommon.showDownloadedFile); }); 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: {} }, }); assert.calledOnce(global.DownloadsCommon.openDownload); }); it("should copy the file on UNINIT", () => { // DownloadsManager._downloadData needs to exist first downloadsManager.onAction({ type: at.UNINIT }); assert.calledOnce(global.DownloadsCommon.getData().removeView); }); 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" }, }); assert.notCalled(global.DownloadsCommon.showDownloadedFile); }); }); 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); assert.ok( downloadsManager._downloadItems.has("https://site.com/newDownload.mov") ); }); 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; assert.equal(results.size, 1); }); it("should not return any downloads if no threshold is provided", async () => { downloadsManager.onDownloadAdded(newDownload); const results = await downloadsManager.getDownloads(null, {}); assert.equal(results.length, 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, }); assert.equal(results.length, 1); assert.equal(results[0].url, 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 } ); assert.equal(results.length, 1); assert.equal(results[0].url, newDownload.source.url); }); it("should dispatch DOWNLOAD_CHANGED when adding a download", () => { downloadsManager._store.dispatch = sinon.spy(); downloadsManager._downloadTimer = null; // Nuke the timer downloadsManager.onDownloadAdded(newDownload); assert.calledOnce(downloadsManager._store.dispatch); }); 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: () => {}, }; sinon.stub(aDownload, "refresh").returns(Promise.resolve()); downloadsManager.onDownloadAdded(aDownload); await downloadsManager.getDownloads(Infinity, { numItems: 5, onlySucceeded: true, onlyExists: true, }); assert.calledOnce(aDownload.refresh); }); 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: () => {}, }; sinon.stub(aDownload, "refresh").returns(Promise.resolve()); downloadsManager.onDownloadAdded(aDownload); await downloadsManager.getDownloads(Infinity, { numItems: 5, onlySucceeded: true, }); assert.notCalled(aDownload.refresh); }); it("should only return downloads that exist if specified", async () => { const nonExistantDownload = { source: { url: "https://site.com/nonExistantDownload.pdf" }, 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, }); assert.equal(results.length, 1); assert.equal(results[0].url, newDownload.source.url); }); it("should return all downloads that either exist or don't exist if not specified", async () => { const nonExistantDownload = { source: { url: "https://site.com/nonExistantDownload.pdf" }, 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, }); assert.equal(results.length, 2); assert.equal(results[0].url, newDownload.source.url); assert.equal(results[1].url, nonExistantDownload.source.url); }); it("should return only unblocked downloads", async () => { const nonExistantDownload = { source: { url: "https://site.com/nonExistantDownload.pdf" }, 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); globals.set("NewTabUtils", { blockedLinks: { isBlocked: item => item.url === nonExistantDownload.source.url, }, }); const results = await downloadsManager.getDownloads(Infinity, { numItems: 5, onlySucceeded: true, }); assert.equal(results.length, 1); assert.propertyVal(results[0], "url", newDownload.source.url); }); it("should only return downloads that were successful if specified", async () => { const nonSuccessfulDownload = { source: { url: "https://site.com/nonSuccessfulDownload.pdf" }, 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, }); assert.equal(results.length, 1); assert.equal(results[0].url, newDownload.source.url); }); it("should return all downloads that were either successful or not if not specified", async () => { const nonExistantDownload = { source: { url: "https://site.com/nonExistantDownload.pdf" }, 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, }); assert.equal(results.length, 2); assert.equal(results[0].url, newDownload.source.url); assert.equal(results[1].url, 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, }); assert.equal(results.length, 3); assert.equal(results[0].url, newDownload.source.url); assert.equal(results[1].url, olderDownload2.source.url); assert.equal(results[2].url, olderDownload1.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, }); assert.equal(results.length, 1); assert.equal(results[0].description, "1.5 MB"); // see unit-entry.js to see where this comes from }); }); 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, }); assert.deepEqual(results, []); }); it("should dispatch DOWNLOAD_CHANGED when removing a download", () => { downloadsManager._store.dispatch = sinon.spy(); downloadsManager.onDownloadRemoved({ source: { url: "https://site.com/removeMe.mov" }, }); assert.calledOnce(downloadsManager._store.dispatch); }); }); });