|
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 "use strict"; |
|
7 |
|
8 /** |
|
9 * Provides infrastructure for automated download components tests. |
|
10 * (adapted from browser/component/downloads test's head.js) |
|
11 */ |
|
12 |
|
13 //////////////////////////////////////////////////////////////////////////////// |
|
14 //// Globals |
|
15 |
|
16 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
|
17 "resource://gre/modules/FileUtils.jsm"); |
|
18 |
|
19 const nsIDM = Ci.nsIDownloadManager; |
|
20 |
|
21 //////////////////////////////////////////////////////////////////////////////// |
|
22 // Test Helpers |
|
23 |
|
24 var { spawn } = Task; |
|
25 |
|
26 function equalStrings(){ |
|
27 let ref = ""+arguments[0]; |
|
28 for (let i=1; i<arguments.length; i++){ |
|
29 if (ref !== ""+arguments[i]) { |
|
30 info("equalStrings failure: " + ref + " != " + arguments[i]); |
|
31 return false |
|
32 } |
|
33 } |
|
34 return true; |
|
35 } |
|
36 |
|
37 function equalNumbers(){ |
|
38 let ref = Number(arguments[0]); |
|
39 for (let i=1; i<arguments.length; i++){ |
|
40 if (ref !== Number(arguments[i])) return false; |
|
41 if (ref !== Number(arguments[i])) { |
|
42 info("equalNumbers failure: " + ref + " != " + Number(arguments[i])); |
|
43 return false |
|
44 } |
|
45 } |
|
46 return true; |
|
47 } |
|
48 |
|
49 function getPromisedDbResult(aStatement) { |
|
50 let dbConnection = MetroDownloadsView.manager.DBConnection; |
|
51 let statement = ("string" == typeof aStatement) ? |
|
52 dbConnection.createAsyncStatement( |
|
53 aStatement |
|
54 ) : aStatement; |
|
55 |
|
56 let deferred = Promise.defer(), |
|
57 resultRows = [], |
|
58 err = null; |
|
59 try { |
|
60 statement.executeAsync({ |
|
61 handleResult: function(aResultSet) { |
|
62 let row; |
|
63 if(!aResultSet) { |
|
64 return; |
|
65 } |
|
66 while ((row = aResultSet.getNextRow())){ |
|
67 resultRows.push(row); |
|
68 } |
|
69 }, |
|
70 handleError: function(aError) { |
|
71 Cu.reportError(aError); |
|
72 err = aError; |
|
73 }, |
|
74 handleCompletion: function(){ |
|
75 if (err) { |
|
76 deferred.reject(err); |
|
77 } else { |
|
78 deferred.resolve(resultRows); |
|
79 } |
|
80 } |
|
81 }); |
|
82 } finally { |
|
83 statement.finalize(); |
|
84 } |
|
85 return deferred.promise; |
|
86 } |
|
87 |
|
88 let gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]); |
|
89 gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); |
|
90 registerCleanupFunction(function () { |
|
91 gTestTargetFile.remove(false); |
|
92 PanelUI.hide(); |
|
93 }); |
|
94 |
|
95 /** |
|
96 * This object contains a property for each column in the downloads table. |
|
97 */ |
|
98 let gDownloadRowTemplate = { |
|
99 name: "test-download.txt", |
|
100 source: "http://www.example.com/test-download.txt", |
|
101 target: NetUtil.newURI(gTestTargetFile).spec, |
|
102 startTime: 1180493839859230, |
|
103 endTime: 1180493839859234, |
|
104 state: nsIDM.DOWNLOAD_FINISHED, |
|
105 currBytes: 0, |
|
106 maxBytes: -1, |
|
107 preferredAction: 0, |
|
108 autoResume: 0 |
|
109 }; |
|
110 |
|
111 //////////////////////////////////////////////////////////////////////////////// |
|
112 // Test Infrastructure |
|
113 |
|
114 function test() { |
|
115 runTests(); |
|
116 } |
|
117 |
|
118 ///////////////////////////////////// |
|
119 // shared test setup |
|
120 function resetDownloads(){ |
|
121 // clear out existing and any pending downloads in the db |
|
122 // returns a promise |
|
123 |
|
124 let promisedResult = getPromisedDbResult( |
|
125 "DELETE FROM moz_downloads" |
|
126 ); |
|
127 return promisedResult.then(function(aResult){ |
|
128 // // Reset any prefs that might have been changed. |
|
129 // Services.prefs.clearUserPref("browser.download.panel.shown"); |
|
130 |
|
131 // Ensure that data is unloaded. |
|
132 let dlMgr = MetroDownloadsView.manager; |
|
133 let dlsToRemove = []; |
|
134 // Clear all completed/cancelled downloads |
|
135 dlMgr.cleanUp(); |
|
136 dlMgr.cleanUpPrivate(); |
|
137 |
|
138 // Queue up all active ones as well |
|
139 for (let dlsEnum of [dlMgr.activeDownloads, dlMgr.activePrivateDownloads]) { |
|
140 while (dlsEnum.hasMoreElements()) { |
|
141 dlsToRemove.push(dlsEnum.next()); |
|
142 } |
|
143 } |
|
144 // Remove any queued up active downloads |
|
145 dlsToRemove.forEach(function (dl) { |
|
146 dl.remove(); |
|
147 }); |
|
148 }); |
|
149 } |
|
150 |
|
151 function addDownloadRow(aDataRow) { |
|
152 let deferredInsert = Promise.defer(); |
|
153 let dataRow = aDataRow; |
|
154 |
|
155 let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); |
|
156 let db = dm.DBConnection; |
|
157 |
|
158 let columnNames = Object.keys(gDownloadRowTemplate).join(", "); |
|
159 let parameterNames = Object.keys(gDownloadRowTemplate) |
|
160 .map(function(n) ":" + n) |
|
161 .join(", "); |
|
162 |
|
163 let statement = db.createAsyncStatement( |
|
164 "INSERT INTO moz_downloads (" + columnNames + |
|
165 ", guid) VALUES(" + parameterNames + ", GENERATE_GUID())"); |
|
166 |
|
167 // Populate insert parameters from the provided data. |
|
168 for (let columnName in gDownloadRowTemplate) { |
|
169 if (!(columnName in dataRow)) { |
|
170 // Update the provided row object with data from the global template, |
|
171 // for columns whose value is not provided explicitly. |
|
172 dataRow[columnName] = gDownloadRowTemplate[columnName]; |
|
173 } |
|
174 statement.params[columnName] = dataRow[columnName]; |
|
175 } |
|
176 |
|
177 // Run the statement asynchronously and wait. |
|
178 let promisedDownloads = getPromisedDbResult( |
|
179 statement |
|
180 ); |
|
181 yield promisedDownloads.then(function(){ |
|
182 let newItemId = db.lastInsertRowID; |
|
183 let download = dm.getDownload(newItemId); |
|
184 deferredInsert.resolve(download); |
|
185 }); |
|
186 } |
|
187 |
|
188 function gen_addDownloadRows(aDataRows){ |
|
189 if (!aDataRows.length) { |
|
190 yield null; |
|
191 } |
|
192 |
|
193 try { |
|
194 // Add each of the provided downloads in reverse. |
|
195 for (let i = aDataRows.length - 1; i >= 0; i--) { |
|
196 let dataRow = aDataRows[i]; |
|
197 let download = yield addDownloadRow(dataRow); |
|
198 |
|
199 // At each iteration, ensure that the start and end time in the global |
|
200 // template is distinct, as these column are used to sort each download |
|
201 // in its category. |
|
202 gDownloadRowTemplate.startTime++; |
|
203 gDownloadRowTemplate.endTime++; |
|
204 } |
|
205 } finally { |
|
206 info("gen_addDownloadRows, finally"); |
|
207 } |
|
208 } |
|
209 |
|
210 ///////////////////////////////////// |
|
211 // Test implementations |
|
212 |
|
213 gTests.push({ |
|
214 desc: "zero downloads", |
|
215 run: function () { |
|
216 yield resetDownloads(); |
|
217 todo(false, "Test there are no visible notifications with an empty db."); |
|
218 } |
|
219 }); |
|
220 |
|
221 /** |
|
222 * Make sure the downloads panel can display items in the right order and |
|
223 * contains the expected data. |
|
224 */ |
|
225 gTests.push({ |
|
226 desc: "Show downloads", |
|
227 run: function(){ |
|
228 // Display one of each download state. |
|
229 let DownloadData = [ |
|
230 { endTime: 1180493839859239, state: nsIDM.DOWNLOAD_NOTSTARTED }, |
|
231 { endTime: 1180493839859238, state: nsIDM.DOWNLOAD_DOWNLOADING }, |
|
232 { endTime: 1180493839859237, state: nsIDM.DOWNLOAD_PAUSED }, |
|
233 { endTime: 1180493839859236, state: nsIDM.DOWNLOAD_SCANNING }, |
|
234 { endTime: 1180493839859235, state: nsIDM.DOWNLOAD_QUEUED }, |
|
235 { endTime: 1180493839859234, state: nsIDM.DOWNLOAD_FINISHED }, |
|
236 { endTime: 1180493839859233, state: nsIDM.DOWNLOAD_FAILED }, |
|
237 { endTime: 1180493839859232, state: nsIDM.DOWNLOAD_CANCELED }, |
|
238 { endTime: 1180493839859231, state: nsIDM.DOWNLOAD_BLOCKED_PARENTAL }, |
|
239 { endTime: 1180493839859230, state: nsIDM.DOWNLOAD_DIRTY }, |
|
240 { endTime: 1180493839859229, state: nsIDM.DOWNLOAD_BLOCKED_POLICY } |
|
241 ]; |
|
242 |
|
243 yield resetDownloads(); |
|
244 |
|
245 try { |
|
246 // Populate the downloads database with the data required by this test. |
|
247 // we're going to add stuff to the downloads db. |
|
248 yield spawn( gen_addDownloadRows( DownloadData ) ); |
|
249 |
|
250 todo( false, "Check that MetroDownloadsView._progressNotificationInfo and MetroDownloadsView._downloadCount \ |
|
251 have the correct length (DownloadData.length) \ |
|
252 May also test that the correct notifications show up for various states."); |
|
253 |
|
254 todo(false, "Iterate through download objects in MetroDownloadsView._progressNotificationInfo \ |
|
255 and confirm that the downloads they refer to are the same as those in \ |
|
256 DownloadData."); |
|
257 } catch(e) { |
|
258 info("Show downloads, some error: " + e); |
|
259 } |
|
260 finally { |
|
261 // Clean up when the test finishes. |
|
262 yield resetDownloads(); |
|
263 } |
|
264 } |
|
265 }); |
|
266 |
|
267 /** |
|
268 * Make sure the downloads can be removed with the expected result on the notifications |
|
269 */ |
|
270 gTests.push({ |
|
271 desc: "Remove downloads", |
|
272 run: function(){ |
|
273 // Push a few items into the downloads db. |
|
274 let DownloadData = [ |
|
275 { endTime: 1180493839859239, state: nsIDM.DOWNLOAD_FINISHED }, |
|
276 { endTime: 1180493839859238, state: nsIDM.DOWNLOAD_FINISHED }, |
|
277 { endTime: 1180493839859237, state: nsIDM.DOWNLOAD_FINISHED } |
|
278 ]; |
|
279 |
|
280 yield resetDownloads(); |
|
281 |
|
282 try { |
|
283 // Populate the downloads database with the data required by this test. |
|
284 yield spawn( gen_addDownloadRows( DownloadData ) ); |
|
285 |
|
286 let downloadRows = null, |
|
287 promisedDownloads; |
|
288 // get all the downloads from the db |
|
289 promisedDownloads = getPromisedDbResult( |
|
290 "SELECT guid " |
|
291 + "FROM moz_downloads " |
|
292 + "ORDER BY startTime DESC" |
|
293 ).then(function(aRows){ |
|
294 downloadRows = aRows; |
|
295 }, function(aError){ |
|
296 throw aError; |
|
297 }); |
|
298 yield promisedDownloads; |
|
299 |
|
300 is(downloadRows.length, 3, "Correct number of downloads in the db before removal"); |
|
301 |
|
302 todo(false, "Get some download from MetroDownloadsView._progressNotificationInfo, \ |
|
303 confirm that its file exists, then remove it."); |
|
304 |
|
305 // remove is async(?), wait a bit |
|
306 yield waitForMs(0); |
|
307 |
|
308 // get all the downloads from the db |
|
309 downloadRows = null; |
|
310 promisedDownloads = getPromisedDbResult( |
|
311 "SELECT guid " |
|
312 + "FROM moz_downloads " |
|
313 + "ORDER BY startTime DESC" |
|
314 ).then(function(aRows){ |
|
315 downloadRows = aRows; |
|
316 }, function(aError){ |
|
317 throw aError; |
|
318 }); |
|
319 yield promisedDownloads; |
|
320 |
|
321 todo(false, "confirm that the removed download is no longer in the database \ |
|
322 and its file no longer exists."); |
|
323 |
|
324 } catch(e) { |
|
325 info("Remove downloads, some error: " + e); |
|
326 } |
|
327 finally { |
|
328 // Clean up when the test finishes. |
|
329 yield resetDownloads(); |
|
330 } |
|
331 } |
|
332 }); |
|
333 |
|
334 /** |
|
335 * Make sure the cancelled/aborted downloads are handled correctly. |
|
336 */ |
|
337 gTests.push({ |
|
338 desc: "Cancel/Abort Downloads", |
|
339 run: function(){ |
|
340 todo(false, "Ensure that a cancelled/aborted download is in the correct state \ |
|
341 including correct values for state variables (e.g. _downloadCount, _downloadsInProgress) \ |
|
342 and the existence of the downloaded file."); |
|
343 } |
|
344 }); |
|
345 |
|
346 /** |
|
347 * Make sure download notifications are moved when we close tabs. |
|
348 */ |
|
349 gTests.push({ |
|
350 desc: "Download notifications in closed tabs", |
|
351 setUp: function() { |
|
352 // put up a couple notifications on the initial tab |
|
353 let notificationBox = Browser.getNotificationBox(); |
|
354 notificationBox.appendNotification("not important", "low-priority-thing", "", notificationBox.PRIORITY_INFO_LOW, []); |
|
355 notificationBox.appendNotification("so important", "high-priority-thing", "", notificationBox.PRIORITY_CRITICAL_HIGH, []); |
|
356 |
|
357 // open a new tab where we'll conduct the test |
|
358 yield addTab("about:mozilla"); |
|
359 }, |
|
360 run: function(){ |
|
361 let notificationBox = Browser.getNotificationBox(); |
|
362 let notn = MetroDownloadsView.showNotification("download-progress", "test message", [], |
|
363 notificationBox.PRIORITY_WARNING_LOW); |
|
364 Browser.closeTab(Browser.selectedTab); |
|
365 |
|
366 yield waitForEvent(Elements.tabList, "TabRemove"); |
|
367 |
|
368 // expected behavior when a tab is closed while a download notification is showing: |
|
369 // * the notification remains visible as long as a next tab/browser exists |
|
370 // * normal rules about priority apply |
|
371 // * notifications - including any pre-existing ones - display in expected order |
|
372 let nextBox = Browser.getNotificationBox(); |
|
373 let currentNotification; |
|
374 |
|
375 ok(nextBox.getNotificationWithValue("download-progress"), "notification was moved to next tab"); |
|
376 |
|
377 currentNotification = nextBox.currentNotification; |
|
378 is(currentNotification.value, "high-priority-thing", "high priority notification is current"); |
|
379 currentNotification.close(); |
|
380 |
|
381 currentNotification = nextBox.currentNotification; |
|
382 is(currentNotification.value, "download-progress", "download notification is next"); |
|
383 currentNotification.close(); |
|
384 |
|
385 currentNotification = nextBox.currentNotification; |
|
386 is(currentNotification.value, "low-priority-thing", "low priority notification is next"); |
|
387 currentNotification.close(); |
|
388 } |
|
389 }); |