zotero/test/content/support.js
Dan Stillman 5ba344516e Update PDF tool handling in tests
The test runner now downloads and caches the PDF tools for the current
platform within the test data directory and only redownloads them when
out of date, and it updates the download URL so that the full-text code
pulls from the cache directory via a file:// URL.

The installPDFTools() support function now installs the files directly
instead of going through the prefs, and a new uninstallPDFTools()
function removes the tools. Since the presence of the PDF tools can
affect other tests, tests that need the tools should install them in a
before() and uninstall them in an after(), leaving most tests to run
without PDF indexing.

This also adds a callback to the waitForWindow() support function. If a
modal dialog is opened, it blocks the next promise handler from running,
so a callback has to be used to interact with and close the dialog
immediately.
2015-05-31 23:50:26 -04:00

257 lines
7.2 KiB
JavaScript

/**
* Waits for a DOM event on the specified node. Returns a promise
* resolved with the event.
*/
function waitForDOMEvent(target, event, capture) {
var deferred = Zotero.Promise.defer();
var func = function(ev) {
target.removeEventListener("event", func, capture);
deferred.resolve(ev);
}
target.addEventListener(event, func, capture);
return deferred.promise;
}
/**
* Open a chrome window and return a promise for the window
*
* @return {Promise<ChromeWindow>}
*/
function loadWindow(winurl, argument) {
var win = window.openDialog(winurl, "_blank", "chrome", argument);
return waitForDOMEvent(win, "load").then(function() {
return win;
});
}
/**
* Open a browser window and return a promise for the window
*
* @return {Promise<ChromeWindow>}
*/
function loadBrowserWindow() {
var win = window.openDialog("chrome://browser/content/browser.xul", "", "all,height=400,width=1000");
return waitForDOMEvent(win, "load").then(function() {
return win;
});
}
/**
* Loads a Zotero pane in a new window and selects My Library. Returns the containing window.
*/
var loadZoteroPane = Zotero.Promise.coroutine(function* () {
var win = yield loadBrowserWindow();
Zotero.Prefs.clear('lastViewedFolder');
win.ZoteroOverlay.toggleDisplay(true);
// Hack to wait for pane load to finish. This is the same hack
// we use in ZoteroPane.js, so either it's not good enough
// there or it should be good enough here.
yield Zotero.Promise.delay(52);
yield waitForItemsLoad(win, 0);
return win;
});
/**
* Waits for a window with a specific URL to open. Returns a promise for the window, and
* optionally passes the window to a callback immediately for use with modal dialogs,
* which prevent async code from continuing
*/
function waitForWindow(uri, callback) {
var deferred = Zotero.Promise.defer();
Components.utils.import("resource://gre/modules/Services.jsm");
var loadobserver = function(ev) {
ev.originalTarget.removeEventListener("load", loadobserver, false);
if(ev.target.location.href == uri) {
Services.ww.unregisterNotification(winobserver);
var win = ev.target.docShell
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
if (callback) {
callback(win);
}
deferred.resolve(win);
}
};
var winobserver = {"observe":function(subject, topic, data) {
if(topic != "domwindowopened") return;
var win = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("load", loadobserver, false);
}};
Services.ww.registerNotification(winobserver);
return deferred.promise;
}
var selectLibrary = Zotero.Promise.coroutine(function* (win) {
yield win.ZoteroPane.collectionsView.selectLibrary(Zotero.Libraries.userLibraryID);
yield waitForItemsLoad(win);
});
var waitForItemsLoad = function (win, collectionRowToSelect) {
var resolve;
var promise = new Zotero.Promise(() => resolve = arguments[0]);
var zp = win.ZoteroPane;
var cv = zp.collectionsView;
cv.addEventListener('load', function () {
if (collectionRowToSelect !== undefined) {
cv.selection.select(collectionRowToSelect);
}
zp.addEventListener('itemsLoaded', function () {
resolve();
});
});
return promise;
}
/**
* Waits for a single item event. Returns a promise for the item ID(s).
*/
function waitForItemEvent(event) {
var deferred = Zotero.Promise.defer();
var notifierID = Zotero.Notifier.registerObserver({notify:function(ev, type, ids, extraData) {
if(ev == event) {
Zotero.Notifier.unregisterObserver(notifierID);
deferred.resolve(ids);
}
}}, ["item"]);
return deferred.promise;
}
/**
* Looks for windows with a specific URL.
*/
function getWindows(uri) {
Components.utils.import("resource://gre/modules/Services.jsm");
var enumerator = Services.wm.getEnumerator(null);
var wins = [];
while(enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if(win.location == uri) {
wins.push(win);
}
}
return wins;
}
/**
* Resolve a promise when a specified callback returns true. interval
* specifies the interval between checks. timeout specifies when we
* should assume failure.
*/
function waitForCallback(cb, interval, timeout) {
var deferred = Zotero.Promise.defer();
if(interval === undefined) interval = 100;
if(timeout === undefined) timeout = 10000;
var start = Date.now();
var id = setInterval(function() {
var success = cb();
if(success) {
clearInterval(id);
deferred.resolve(success);
} else if(Date.now() - start > timeout*1000) {
clearInterval(id);
deferred.reject(new Error("Promise timed out"));
}
}, interval);
return deferred.promise;
}
//
// Data objects
//
function createUnsavedDataObject(objectType, params) {
params = params || {};
if (objectType == 'item') {
var param = params.itemType || 'book';
}
var obj = new Zotero[Zotero.Utilities.capitalize(objectType)](param);
switch (objectType) {
case 'collection':
case 'search':
obj.name = params.name !== undefined ? params.name : "Test";
break;
}
var allowedParams = ['parentID', 'parentKey', 'synced', 'version'];
allowedParams.forEach(function (param) {
if (params[param] !== undefined) {
obj[param] = params[param];
}
})
return obj;
}
var createDataObject = Zotero.Promise.coroutine(function* (objectType, params, saveOptions) {
var obj = createUnsavedDataObject(objectType, params);
yield obj.saveTx(saveOptions);
return obj;
});
/**
* Return a promise for the error thrown by a promise, or false if none
*/
function getPromiseError(promise) {
return promise.thenReturn(false).catch(e => e);
}
/**
* Ensures that the PDF tools are installed, or installs them if not.
*
* @return {Promise}
*/
var installPDFTools = Zotero.Promise.coroutine(function* () {
if(Zotero.Fulltext.pdfConverterIsRegistered() && Zotero.Fulltext.pdfInfoIsRegistered()) {
return;
}
var version = yield Zotero.Fulltext.getLatestPDFToolsVersion();
yield Zotero.Fulltext.downloadPDFTool('info', version);
yield Zotero.Fulltext.downloadPDFTool('converter', version);
});
/**
* @return {Promise}
*/
function uninstallPDFTools() {
return Zotero.Fulltext.removePDFTools();
}
/**
* Returns a promise for the nsIFile corresponding to the test data
* directory (i.e., test/tests/data)
*/
function getTestDataDirectory() {
Components.utils.import("resource://gre/modules/Services.jsm");
var resource = Services.io.getProtocolHandler("resource").
QueryInterface(Components.interfaces.nsIResProtocolHandler),
resURI = Services.io.newURI("resource://zotero-unit-tests/data", null, null);
return Services.io.newURI(resource.resolveURI(resURI), null, null).
QueryInterface(Components.interfaces.nsIFileURL).file;
}
/**
* Resets the Zotero DB and restarts Zotero. Returns a promise resolved
* when this finishes.
*/
function resetDB() {
var db = Zotero.getZoteroDatabase();
return Zotero.reinit(function() {
db.remove(false);
}).then(function() {
return Zotero.Schema.schemaUpdatePromise;
});
}
/**
* Imports an attachment from a test file.
* @param {string} filename - The filename to import (in data directory)
* @return {Promise<Zotero.Item>}
*/
function importFileAttachment(filename) {
let testfile = getTestDataDirectory();
filename.split('/').forEach((part) => testfile.append(part));
return Zotero.Attachments.importFromFile({file: testfile});
}