DownloadsManager.test.js (14771B)
1 import { actionTypes as at } from "common/Actions.mjs"; 2 import { DownloadsManager } from "lib/DownloadsManager.sys.mjs"; 3 import { GlobalOverrider } from "test/unit/utils"; 4 5 describe("Downloads Manager", () => { 6 let downloadsManager; 7 let globals; 8 const DOWNLOAD_URL = "https://site.com/download.mov"; 9 10 beforeEach(() => { 11 globals = new GlobalOverrider(); 12 global.Cc["@mozilla.org/timer;1"] = { 13 createInstance() { 14 return { 15 initWithCallback: sinon.stub().callsFake(callback => callback()), 16 cancel: sinon.spy(), 17 }; 18 }, 19 }; 20 21 globals.set("DownloadsCommon", { 22 getData: sinon.stub().returns({ 23 addView: sinon.stub(), 24 removeView: sinon.stub(), 25 }), 26 copyDownloadLink: sinon.stub(), 27 deleteDownload: sinon.stub().returns(Promise.resolve()), 28 openDownload: sinon.stub(), 29 showDownloadedFile: sinon.stub(), 30 }); 31 32 globals.set("BrowserUtils", { 33 whereToOpenLink: sinon.stub().returns("current"), 34 }); 35 36 downloadsManager = new DownloadsManager(); 37 downloadsManager.init({ dispatch() {} }); 38 downloadsManager.onDownloadAdded({ 39 source: { url: DOWNLOAD_URL }, 40 endTime: Date.now(), 41 target: { path: "/path/to/download.mov", exists: true }, 42 succeeded: true, 43 refresh: async () => {}, 44 }); 45 assert.ok(downloadsManager._downloadItems.has(DOWNLOAD_URL)); 46 47 globals.set("NewTabUtils", { blockedLinks: { isBlocked() {} } }); 48 }); 49 afterEach(() => { 50 downloadsManager._downloadItems.clear(); 51 globals.restore(); 52 }); 53 describe("#init", () => { 54 it("should add a DownloadsCommon view on init", () => { 55 downloadsManager.init({ dispatch() {} }); 56 assert.calledTwice(global.DownloadsCommon.getData().addView); 57 }); 58 }); 59 describe("#onAction", () => { 60 it("should copy the file on COPY_DOWNLOAD_LINK", () => { 61 downloadsManager.onAction({ 62 type: at.COPY_DOWNLOAD_LINK, 63 data: { url: DOWNLOAD_URL }, 64 }); 65 assert.calledOnce(global.DownloadsCommon.copyDownloadLink); 66 }); 67 it("should remove the file on REMOVE_DOWNLOAD_FILE", () => { 68 downloadsManager.onAction({ 69 type: at.REMOVE_DOWNLOAD_FILE, 70 data: { url: DOWNLOAD_URL }, 71 }); 72 assert.calledOnce(global.DownloadsCommon.deleteDownload); 73 }); 74 it("should show the file on SHOW_DOWNLOAD_FILE", () => { 75 downloadsManager.onAction({ 76 type: at.SHOW_DOWNLOAD_FILE, 77 data: { url: DOWNLOAD_URL }, 78 }); 79 assert.calledOnce(global.DownloadsCommon.showDownloadedFile); 80 }); 81 it("should open the file on OPEN_DOWNLOAD_FILE if the type is download", () => { 82 downloadsManager.onAction({ 83 type: at.OPEN_DOWNLOAD_FILE, 84 data: { url: DOWNLOAD_URL, type: "download" }, 85 _target: { browser: {} }, 86 }); 87 assert.calledOnce(global.DownloadsCommon.openDownload); 88 }); 89 it("should copy the file on UNINIT", () => { 90 // DownloadsManager._downloadData needs to exist first 91 downloadsManager.onAction({ type: at.UNINIT }); 92 assert.calledOnce(global.DownloadsCommon.getData().removeView); 93 }); 94 it("should not execute a download command if we do not have the correct url", () => { 95 downloadsManager.onAction({ 96 type: at.SHOW_DOWNLOAD_FILE, 97 data: { url: "unknown_url" }, 98 }); 99 assert.notCalled(global.DownloadsCommon.showDownloadedFile); 100 }); 101 }); 102 describe("#onDownloadAdded", () => { 103 let newDownload; 104 beforeEach(() => { 105 downloadsManager._downloadItems.clear(); 106 newDownload = { 107 source: { url: "https://site.com/newDownload.mov" }, 108 endTime: Date.now(), 109 target: { path: "/path/to/newDownload.mov", exists: true }, 110 succeeded: true, 111 refresh: async () => {}, 112 }; 113 }); 114 afterEach(() => { 115 downloadsManager._downloadItems.clear(); 116 }); 117 it("should add a download on onDownloadAdded", () => { 118 downloadsManager.onDownloadAdded(newDownload); 119 assert.ok( 120 downloadsManager._downloadItems.has("https://site.com/newDownload.mov") 121 ); 122 }); 123 it("should not add a download if it already exists", () => { 124 downloadsManager.onDownloadAdded(newDownload); 125 downloadsManager.onDownloadAdded(newDownload); 126 downloadsManager.onDownloadAdded(newDownload); 127 downloadsManager.onDownloadAdded(newDownload); 128 const results = downloadsManager._downloadItems; 129 assert.equal(results.size, 1); 130 }); 131 it("should not return any downloads if no threshold is provided", async () => { 132 downloadsManager.onDownloadAdded(newDownload); 133 const results = await downloadsManager.getDownloads(null, {}); 134 assert.equal(results.length, 0); 135 }); 136 it("should stop at numItems when it found one it's looking for", async () => { 137 const aDownload = { 138 source: { url: "https://site.com/aDownload.pdf" }, 139 endTime: Date.now(), 140 target: { path: "/path/to/aDownload.pdf", exists: true }, 141 succeeded: true, 142 refresh: async () => {}, 143 }; 144 downloadsManager.onDownloadAdded(aDownload); 145 downloadsManager.onDownloadAdded(newDownload); 146 const results = await downloadsManager.getDownloads(Infinity, { 147 numItems: 1, 148 onlySucceeded: true, 149 onlyExists: true, 150 }); 151 assert.equal(results.length, 1); 152 assert.equal(results[0].url, aDownload.source.url); 153 }); 154 it("should get all the downloads younger than the threshold provided", async () => { 155 const oldDownload = { 156 source: { url: "https://site.com/oldDownload.pdf" }, 157 endTime: Date.now() - 40 * 60 * 60 * 1000, 158 target: { path: "/path/to/oldDownload.pdf", exists: true }, 159 succeeded: true, 160 refresh: async () => {}, 161 }; 162 // Add an old download (older than 36 hours in this case) 163 downloadsManager.onDownloadAdded(oldDownload); 164 downloadsManager.onDownloadAdded(newDownload); 165 const RECENT_DOWNLOAD_THRESHOLD = 36 * 60 * 60 * 1000; 166 const results = await downloadsManager.getDownloads( 167 RECENT_DOWNLOAD_THRESHOLD, 168 { numItems: 5, onlySucceeded: true, onlyExists: true } 169 ); 170 assert.equal(results.length, 1); 171 assert.equal(results[0].url, newDownload.source.url); 172 }); 173 it("should dispatch DOWNLOAD_CHANGED when adding a download", () => { 174 downloadsManager._store.dispatch = sinon.spy(); 175 downloadsManager._downloadTimer = null; // Nuke the timer 176 downloadsManager.onDownloadAdded(newDownload); 177 assert.calledOnce(downloadsManager._store.dispatch); 178 }); 179 it("should refresh the downloads if onlyExists is true", async () => { 180 const aDownload = { 181 source: { url: "https://site.com/aDownload.pdf" }, 182 endTime: Date.now() - 40 * 60 * 60 * 1000, 183 target: { path: "/path/to/aDownload.pdf", exists: true }, 184 succeeded: true, 185 refresh: () => {}, 186 }; 187 sinon.stub(aDownload, "refresh").returns(Promise.resolve()); 188 downloadsManager.onDownloadAdded(aDownload); 189 await downloadsManager.getDownloads(Infinity, { 190 numItems: 5, 191 onlySucceeded: true, 192 onlyExists: true, 193 }); 194 assert.calledOnce(aDownload.refresh); 195 }); 196 it("should not refresh the downloads if onlyExists is false (by default)", async () => { 197 const aDownload = { 198 source: { url: "https://site.com/aDownload.pdf" }, 199 endTime: Date.now() - 40 * 60 * 60 * 1000, 200 target: { path: "/path/to/aDownload.pdf", exists: true }, 201 succeeded: true, 202 refresh: () => {}, 203 }; 204 sinon.stub(aDownload, "refresh").returns(Promise.resolve()); 205 downloadsManager.onDownloadAdded(aDownload); 206 await downloadsManager.getDownloads(Infinity, { 207 numItems: 5, 208 onlySucceeded: true, 209 }); 210 assert.notCalled(aDownload.refresh); 211 }); 212 it("should only return downloads that exist if specified", async () => { 213 const nonExistantDownload = { 214 source: { url: "https://site.com/nonExistantDownload.pdf" }, 215 endTime: Date.now() - 40 * 60 * 60 * 1000, 216 target: { path: "/path/to/nonExistantDownload.pdf", exists: false }, 217 succeeded: true, 218 refresh: async () => {}, 219 }; 220 downloadsManager.onDownloadAdded(newDownload); 221 downloadsManager.onDownloadAdded(nonExistantDownload); 222 const results = await downloadsManager.getDownloads(Infinity, { 223 numItems: 5, 224 onlySucceeded: true, 225 onlyExists: true, 226 }); 227 assert.equal(results.length, 1); 228 assert.equal(results[0].url, newDownload.source.url); 229 }); 230 it("should return all downloads that either exist or don't exist if not specified", async () => { 231 const nonExistantDownload = { 232 source: { url: "https://site.com/nonExistantDownload.pdf" }, 233 endTime: Date.now() - 40 * 60 * 60 * 1000, 234 target: { path: "/path/to/nonExistantDownload.pdf", exists: false }, 235 succeeded: true, 236 refresh: async () => {}, 237 }; 238 downloadsManager.onDownloadAdded(newDownload); 239 downloadsManager.onDownloadAdded(nonExistantDownload); 240 const results = await downloadsManager.getDownloads(Infinity, { 241 numItems: 5, 242 onlySucceeded: true, 243 }); 244 assert.equal(results.length, 2); 245 assert.equal(results[0].url, newDownload.source.url); 246 assert.equal(results[1].url, nonExistantDownload.source.url); 247 }); 248 it("should return only unblocked downloads", async () => { 249 const nonExistantDownload = { 250 source: { url: "https://site.com/nonExistantDownload.pdf" }, 251 endTime: Date.now() - 40 * 60 * 60 * 1000, 252 target: { path: "/path/to/nonExistantDownload.pdf", exists: false }, 253 succeeded: true, 254 refresh: async () => {}, 255 }; 256 downloadsManager.onDownloadAdded(newDownload); 257 downloadsManager.onDownloadAdded(nonExistantDownload); 258 globals.set("NewTabUtils", { 259 blockedLinks: { 260 isBlocked: item => item.url === nonExistantDownload.source.url, 261 }, 262 }); 263 264 const results = await downloadsManager.getDownloads(Infinity, { 265 numItems: 5, 266 onlySucceeded: true, 267 }); 268 269 assert.equal(results.length, 1); 270 assert.propertyVal(results[0], "url", newDownload.source.url); 271 }); 272 it("should only return downloads that were successful if specified", async () => { 273 const nonSuccessfulDownload = { 274 source: { url: "https://site.com/nonSuccessfulDownload.pdf" }, 275 endTime: Date.now() - 40 * 60 * 60 * 1000, 276 target: { path: "/path/to/nonSuccessfulDownload.pdf", exists: false }, 277 succeeded: false, 278 refresh: async () => {}, 279 }; 280 downloadsManager.onDownloadAdded(newDownload); 281 downloadsManager.onDownloadAdded(nonSuccessfulDownload); 282 const results = await downloadsManager.getDownloads(Infinity, { 283 numItems: 5, 284 onlySucceeded: true, 285 }); 286 assert.equal(results.length, 1); 287 assert.equal(results[0].url, newDownload.source.url); 288 }); 289 it("should return all downloads that were either successful or not if not specified", async () => { 290 const nonExistantDownload = { 291 source: { url: "https://site.com/nonExistantDownload.pdf" }, 292 endTime: Date.now() - 40 * 60 * 60 * 1000, 293 target: { path: "/path/to/nonExistantDownload.pdf", exists: true }, 294 succeeded: false, 295 refresh: async () => {}, 296 }; 297 downloadsManager.onDownloadAdded(newDownload); 298 downloadsManager.onDownloadAdded(nonExistantDownload); 299 const results = await downloadsManager.getDownloads(Infinity, { 300 numItems: 5, 301 }); 302 assert.equal(results.length, 2); 303 assert.equal(results[0].url, newDownload.source.url); 304 assert.equal(results[1].url, nonExistantDownload.source.url); 305 }); 306 it("should sort the downloads by recency", async () => { 307 const olderDownload1 = { 308 source: { url: "https://site.com/oldDownload1.pdf" }, 309 endTime: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago 310 target: { path: "/path/to/oldDownload1.pdf", exists: true }, 311 succeeded: true, 312 refresh: async () => {}, 313 }; 314 const olderDownload2 = { 315 source: { url: "https://site.com/oldDownload2.pdf" }, 316 endTime: Date.now() - 60 * 60 * 1000, // 1 hour ago 317 target: { path: "/path/to/oldDownload2.pdf", exists: true }, 318 succeeded: true, 319 refresh: async () => {}, 320 }; 321 // Add some older downloads and check that they are in order 322 downloadsManager.onDownloadAdded(olderDownload1); 323 downloadsManager.onDownloadAdded(olderDownload2); 324 downloadsManager.onDownloadAdded(newDownload); 325 const results = await downloadsManager.getDownloads(Infinity, { 326 numItems: 5, 327 onlySucceeded: true, 328 onlyExists: true, 329 }); 330 assert.equal(results.length, 3); 331 assert.equal(results[0].url, newDownload.source.url); 332 assert.equal(results[1].url, olderDownload2.source.url); 333 assert.equal(results[2].url, olderDownload1.source.url); 334 }); 335 it("should format the description properly if there is no file type", async () => { 336 newDownload.target.path = null; 337 downloadsManager.onDownloadAdded(newDownload); 338 const results = await downloadsManager.getDownloads(Infinity, { 339 numItems: 5, 340 onlySucceeded: true, 341 onlyExists: true, 342 }); 343 assert.equal(results.length, 1); 344 assert.equal(results[0].description, "1.5 MB"); // see unit-entry.js to see where this comes from 345 }); 346 }); 347 describe("#onDownloadRemoved", () => { 348 let newDownload; 349 beforeEach(() => { 350 downloadsManager._downloadItems.clear(); 351 newDownload = { 352 source: { url: "https://site.com/removeMe.mov" }, 353 endTime: Date.now(), 354 target: { path: "/path/to/removeMe.mov", exists: true }, 355 succeeded: true, 356 refresh: async () => {}, 357 }; 358 downloadsManager.onDownloadAdded(newDownload); 359 }); 360 it("should remove a download if it exists on onDownloadRemoved", async () => { 361 downloadsManager.onDownloadRemoved({ 362 source: { url: "https://site.com/removeMe.mov" }, 363 }); 364 const results = await downloadsManager.getDownloads(Infinity, { 365 numItems: 5, 366 }); 367 assert.deepEqual(results, []); 368 }); 369 it("should dispatch DOWNLOAD_CHANGED when removing a download", () => { 370 downloadsManager._store.dispatch = sinon.spy(); 371 downloadsManager.onDownloadRemoved({ 372 source: { url: "https://site.com/removeMe.mov" }, 373 }); 374 assert.calledOnce(downloadsManager._store.dispatch); 375 }); 376 }); 377 });