Use Zotero.Item.fromJSON() for saving from translators

Also:
  - Move some canonicalization of items returned by translators to
    Zotero.Translate
  - Make Zotero.Translate#translate return a promise
  - Add tests
This commit is contained in:
Simon Kornblith 2015-06-04 00:47:58 -04:00
parent e10deedc7e
commit 3fc38d750b
5 changed files with 921 additions and 338 deletions

View file

@ -90,14 +90,13 @@ Zotero.Translate.Sandbox = {
const allowedObjects = ["complete", "attachments", "seeAlso", "creators", "tags", "notes"];
delete item.complete;
// Create a new object here, so that we strip the "complete" property
// (But don't create a new object if we're in a child translator, since that
// would be a problem for the sandbox)
var newItem = translate._parentTranslator ? item : {};
for(var i in item) {
var val = item[i];
if(!val && val !== 0) {
// remove null, undefined, and false properties, and convert objects to strings
delete item[i];
continue;
}
if(i === "complete" || (!val && val !== 0)) continue;
var type = typeof val;
var isObject = type === "object" || type === "xml" || type === "function",
@ -105,15 +104,18 @@ Zotero.Translate.Sandbox = {
if(isObject && !shouldBeObject) {
// Convert things that shouldn't be objects to objects
translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to string");
item[i] = val.toString();
newItem[i] = val.toString();
} else if(shouldBeObject && !isObject) {
translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to array");
item[i] = [val];
newItem[i] = [val];
} else if(type === "string") {
// trim strings
item[i] = val.trim();
newItem[i] = val.trim();
} else {
newItem[i] = val;
}
}
item = newItem;
// Clean empty creators
if (item.creators) {
@ -125,6 +127,9 @@ Zotero.Translate.Sandbox = {
}
}
}
// Canonicalize tags
if(item.tags) item.tags = translate._cleanTags(item.tags);
// if we're not supposed to save the item or we're in a child translator,
// just return the item array
@ -137,15 +142,35 @@ Zotero.Translate.Sandbox = {
// We use this within the connector to keep track of items as they are saved
if(!item.id) item.id = Zotero.Utilities.randomString();
// don't save documents as documents in connector, since we can't pass them around
if(Zotero.isConnector) {
if(item.attachments) {
var attachments = item.attachments;
var nAttachments = attachments.length;
for(var j=0; j<nAttachments; j++) {
if(attachments[j].document) {
attachments[j].url = attachments[j].document.documentURI || attachments[j].document.URL;
attachments[j].mimeType = "text/html";
delete attachments[j].document;
for(var j=0; j<attachments.length; j++) {
var attachment = attachments[j];
// Don't save documents as documents in connector, since we can't pass them around
if(Zotero.isConnector && attachment.document) {
attachment.url = attachment.document.documentURI || attachment.document.URL;
attachment.mimeType = "text/html";
delete attachment.document;
}
// Canonicalize tags
if(attachment.tags !== undefined) attachment.tags = translate._cleanTags(attachment.tags);
}
}
if(item.notes) {
var notes = item.notes;
for(var j=0; j<notes.length; j++) {
var note = notes[j];
if(!note) {
notes.splice(j--, 1);
} else if(typeof(note) == "object") {
// Canonicalize tags
if(note.tags !== undefined) note.tags = translate._cleanTags(note.tags);
} else {
// Convert to object
notes[j] = {"note":note.toString()}
}
}
}
@ -320,7 +345,7 @@ Zotero.Translate.Sandbox = {
errorHandlerSet = true;
translation.setHandler("error", function(obj, error) { translate.complete(false, error) });
}
return translation.translate(false);
translation.translate(false);
};
safeTranslator.getTranslatorObject = function(callback) {
@ -717,13 +742,9 @@ Zotero.Translate.Sandbox = {
* @param {SandboxCollection} collection
*/
"_collectionDone":function(translate, collection) {
translate.newCollections.push(collection);
if(translate._libraryID == false) {
translate.newCollections.push(collection);
translate._runHandler("collectionDone", collection);
} else {
var newCollection = translate._itemSaver.saveCollection(collection);
translate.newCollections.push(newCollection);
translate._runHandler("collectionDone", newCollection);
}
},
@ -1139,6 +1160,8 @@ Zotero.Translate.Base.prototype = {
* or NULL for default library;
* if FALSE, don't save items
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
* @returns {Promise} Promise resolved with saved items
* when translation complete
*/
"translate":function(libraryID, saveAttachments) { // initialize properties specific to each translation
if(!this.translator || !this.translator.length) {
@ -1163,6 +1186,22 @@ Zotero.Translate.Base.prototype = {
this._savingAttachments = [];
this._savingItems = 0;
this._waitingForSave = false;
// Attach handlers for promise
var me = this,
deferred = Zotero.Promise.defer();
var doneHandler = function (obj, returnValue) {
if (returnValue) deferred.resolve(me.newItems);
me.removeHandler("done", doneHandler);
me.removeHandler("error", errorHandler);
};
var errorHandler = function (obj, error) {
deferred.reject(error);
me.removeHandler("done", doneHandler);
me.removeHandler("error", errorHandler);
};
this.setHandler("done", doneHandler);
this.setHandler("error", errorHandler);
var me = this;
if(typeof this.translator[0] === "object") {
@ -1174,6 +1213,7 @@ Zotero.Translate.Base.prototype = {
this.translator[0] = translator;
this._loadTranslator(translator).then(function() { me._translateTranslatorLoaded() });
}
return deferred.promise;
},
/**
@ -1368,6 +1408,31 @@ Zotero.Translate.Base.prototype = {
return errorString;
},
/**
* Canonicalize an array of tags such that they are all objects with the tag stored in the
* "tag" property and a type (if specified) is stored in the "type" property
* @returns {Object[]} Array of new tag objects
*/
"_cleanTags":function(tags) {
var newTags = [];
if(!tags) return newTags;
for(var i=0; i<tags.length; i++) {
var tag = tags[i];
if(!tag) continue;
if(typeof(tag) == "object") {
var tagString = tag.tag || tag.name;
if(tagString) {
var newTag = {"tag":tagString};
if(tag.type) newTag.type = tag.type;
newTags.push(newTag);
}
} else {
newTags.push({"tag":tag.toString()});
}
}
return newTags;
},
/**
* Saves items to the database, taking care to defer attachmentProgress notifications
@ -1443,7 +1508,18 @@ Zotero.Translate.Base.prototype = {
*/
"_checkIfDone":function() {
if(!this._savingItems && !this._savingAttachments.length && (!this._currentState || this._waitingForSave)) {
this._runHandler("done", true);
if(this.newCollections) {
var me = this;
this._itemSaver.saveCollections(this.newCollections).then(function (newCollections) {
me.newCollections = newCollections;
me._runHandler("done", true);
}, function (err) {
me._runHandler("error", err);
me._runHandler("done", false);
});
} else {
this._runHandler("done", true);
}
}
},
@ -1737,9 +1813,13 @@ Zotero.Translate.Web.prototype._getParameters = function() {
* Prepare translation
*/
Zotero.Translate.Web.prototype._prepareTranslation = function() {
this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID,
Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], 1,
this.document, this._cookieSandbox, this.location);
this._itemSaver = new Zotero.Translate.ItemSaver({
"libraryID":this._libraryID,
"attachmentMode":Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")],
"forceTagType":1,
"cookieSandbox":this._cookieSandbox,
"baseURI":this.location
});
this.newItems = [];
}
@ -1748,7 +1828,7 @@ Zotero.Translate.Web.prototype._prepareTranslation = function() {
*/
Zotero.Translate.Web.prototype.translate = function(libraryID, saveAttachments, selectedItems) {
this._selectedItems = selectedItems;
Zotero.Translate.Base.prototype.translate.apply(this, [libraryID, saveAttachments]);
return Zotero.Translate.Base.prototype.translate.apply(this, [libraryID, saveAttachments]);
}
/**
@ -2044,10 +2124,12 @@ Zotero.Translate.Import.prototype._prepareTranslation = function() {
getService(Components.interfaces.nsIIOService).newFileURI(this.location);
} catch(e) {}
}
this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID,
Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
undefined, undefined, undefined, baseURI);
this._itemSaver = new Zotero.Translate.ItemSaver({
"libraryID":this._libraryID,
"attachmentMode":Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
"baseURI":baseURI
});
this.newItems = [];
this.newCollections = [];
}
@ -2215,7 +2297,7 @@ Zotero.Translate.Export.prototype.translate = function() {
if(!this.translator || !this.translator.length) {
this.complete(false, new Error("Export translation initiated without setting a translator"));
} else {
Zotero.Translate.Base.prototype.translate.apply(this, arguments);
return Zotero.Translate.Base.prototype.translate.apply(this, arguments);
}
};

View file

@ -23,52 +23,42 @@
***** END LICENSE BLOCK *****
*/
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, document,
cookieSandbox, baseURI) {
/**
* Save translator items
* @constructor
* @param {Object} options
* <li>libraryID - ID of library in which items should be saved</li>
* <li>attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved</li>
* <li>forceTagType - Force tags to specified tag type</li>
* <li>cookieSandbox - Cookie sandbox for attachment requests</li>
* <li>baseURI - URI to which attachment paths should be relative</li>
*/
Zotero.Translate.ItemSaver = function(options) {
// initialize constants
this.newItems = [];
this.newCollections = [];
this._IDMap = {};
// determine library ID
if(libraryID === false) {
this._libraryID = false;
} else if(libraryID === true || libraryID == undefined) {
this._libraryID = null;
if(!options.libraryID) {
this._libraryID = Zotero.Libraries.userLibraryID;
} else {
this._libraryID = libraryID;
this._libraryID = options.libraryID;
}
this.attachmentMode = attachmentMode;
// If group filesEditable==false, don't save attachments
if (typeof this._libraryID == 'number') {
var type = Zotero.Libraries.getType(this._libraryID);
switch (type) {
case 'group':
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this._libraryID);
var group = Zotero.Groups.get(groupID);
if (!group.filesEditable) {
this.attachmentMode = Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
}
break;
}
}
// force tag types if requested
this._forceTagType = forceTagType;
// to set cookies on downloaded files
this._cookieSandbox = cookieSandbox;
this.attachmentMode = Zotero.Libraries.isFilesEditable(this._libraryID) ? options.attachmentMode :
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
this._forceTagType = options.forceTagType;
this._cookieSandbox = options.cookieSandbox;
// the URI to which other URIs are assumed to be relative
if(typeof baseURI === "object" && baseURI instanceof Components.interfaces.nsIURI) {
this._baseURI = baseURI;
this._baseURI = options.baseURI;
} else {
// try to convert to a URI
this._baseURI = null;
try {
this._baseURI = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService).newURI(baseURI, null, null);
getService(Components.interfaces.nsIIOService).newURI(options.baseURI, null, null);
} catch(e) {};
}
};
@ -83,130 +73,146 @@ Zotero.Translate.ItemSaver.prototype = {
* @param items Items in Zotero.Item.toArray() format
* @param {Function} callback A callback to be executed when saving is complete. If saving
* succeeded, this callback will be passed true as the first argument and a list of items
* saved as the second. If
saving failed, the callback will be passed false as the first
* saved as the second. If saving failed, the callback will be passed false as the first
* argument and an error object as the second
* @param {Function} [attachmentCallback] A callback that receives information about attachment
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
*/
"saveItems":function(items, callback, attachmentCallback) {
Zotero.DB.executeTransaction(function* () {
Zotero.spawn(function* () {
try {
var newItems = [];
for each(var item in items) {
// Get typeID, defaulting to "webpage"
var newItem;
var type = (item.itemType ? item.itemType : "webpage");
if(type == "note") { // handle notes differently
newItem = new Zotero.Item('note');
newItem.libraryID = this._libraryID;
if(item.note) newItem.setNote(item.note);
var myID = newItem.save();
newItem = Zotero.Items.get(myID);
} else {
if(type == "attachment") { // handle attachments differently
newItem = this._saveAttachment(item, null, attachmentCallback);
if(!newItem) continue;
var myID = newItem.id;
let newItems = [], standaloneAttachments = [];
yield (Zotero.DB.executeTransaction(function* () {
for (let iitem=0; iitem<items.length; iitem++) {
let item = items[iitem], newItem, myID;
// Type defaults to "webpage"
let type = (item.itemType ? item.itemType : "webpage");
if (type == "note") { // handle notes differently
newItem = yield this._saveNote(item);
} else if (type == "attachment") { // handle attachments differently
standaloneAttachments.push(iitem);
continue;
} else {
var typeID = Zotero.ItemTypes.getID(type);
newItem = new Zotero.Item(typeID);
newItem._libraryID = this._libraryID;
newItem = new Zotero.Item(type);
newItem.libraryID = this._libraryID;
if(item.tags) item.tags = this._cleanTags(item.tags);
this._saveFields(item, newItem);
// handle creators
if(item.creators) {
newItem.setCreators(item.creators);
}
// Need to handle these specially. Put them in a separate object to
// avoid a warning from fromJSON()
let specialFields = {
attachments:item.attachments,
notes:item.notes,
seeAlso:item.seeAlso,
id:item.itemID || item.id
};
if (item.version) item.versionNumber = item.version;
newItem.fromJSON(this._deleteIrrelevantFields(item));
// save item
var myID = yield newItem.save();
newItem = yield Zotero.Items.getAsync(myID);
myID = yield newItem.save();
// handle notes
if(item.notes) {
this._saveNotes(item, myID);
if (specialFields.notes) {
for (let i=0; i<specialFields.notes.length; i++) {
yield this._saveNote(specialFields.notes[i], myID);
}
}
// handle attachments
if(item.attachments) {
for(var i=0; i<item.attachments.length; i++) {
var newAttachment = this._saveAttachment(item.attachments[i], myID, attachmentCallback);
if(typeof newAttachment === "object") {
this._saveTags(item.attachments[i], newAttachment);
}
if (specialFields.attachments) {
for (let i=0; i<specialFields.attachments.length; i++) {
let attachment = specialFields.attachments[i];
// Don't wait for the promise to resolve, since we want to
// signal completion as soon as the items are saved
this._saveAttachment(attachment, myID, attachmentCallback);
}
// Restore the attachments field, since we use it later in
// translation
item.attachments = specialFields.attachments;
}
// handle see also
this._handleRelated(specialFields, newItem);
}
// add to new item list
newItems.push(newItem);
}
}.bind(this)));
if(item.itemID) this._IDMap[item.itemID] = myID;
// handle see also
this._saveTags(item, newItem);
// add to new item list
newItem = Zotero.Items.get(myID);
newItems.push(newItem);
// Handle standalone attachments outside of the transaction
for (let iitem of standaloneAttachments) {
let newItem = yield this._saveAttachment(items[iitem], null, attachmentCallback);
if (newItem) newItems.push(newItem);
}
callback(true, newItems);
} catch(e) {
callback(false, e);
}
}.bind(this));
}, this);
},
"saveCollection": Zotero.Promise.coroutine(function* (collection) {
var collectionsToProcess = [collection];
"saveCollections": Zotero.Promise.coroutine(function* (collections) {
var collectionsToProcess = collections.slice();
var parentIDs = [null];
var topLevelCollection;
while(collectionsToProcess.length) {
var collection = collectionsToProcess.shift();
var parentID = parentIDs.shift();
var newCollection = new Zotero.Collection;
newCollection.libraryID = this._libraryID;
newCollection.name = collection.name;
if (parentID) {
newCollection.parentID = parentID;
}
yield newCollection.save();
if(parentID === null) topLevelCollection = newCollection;
this.newCollections.push(newCollection.id);
var toAdd = [];
for(var i=0; i<collection.children.length; i++) {
var child = collection.children[i];
if(child.type === "collection") {
// do recursive processing of collections
collectionsToProcess.push(child);
parentIDs.push(newCollection.id);
} else {
// add mapped items to collection
if(this._IDMap[child.id]) {
toAdd.push(this._IDMap[child.id]);
var topLevelCollections = [];
yield Zotero.DB.executeTransaction(function* () {
while(collectionsToProcess.length) {
var collection = collectionsToProcess.shift();
var parentID = parentIDs.shift();
var newCollection = new Zotero.Collection;
newCollection.libraryID = this._libraryID;
newCollection.name = collection.name;
if (parentID) {
newCollection.parentID = parentID;
}
yield newCollection.save();
if(parentID === null) topLevelCollections.push(newCollection);
var toAdd = [];
for(var i=0; i<collection.children.length; i++) {
var child = collection.children[i];
if(child.type === "collection") {
// do recursive processing of collections
collectionsToProcess.push(child);
parentIDs.push(newCollection.id);
} else {
Zotero.debug("Translate: Could not map "+child.id+" to an imported item", 2);
// add mapped items to collection
if(this._IDMap[child.id]) {
toAdd.push(this._IDMap[child.id]);
} else {
Zotero.debug("Translate: Could not map "+child.id+" to an imported item", 2);
}
}
}
if(toAdd.length) {
Zotero.debug("Translate: Adding " + toAdd, 5);
yield newCollection.addItems(toAdd);
}
}
if(toAdd.length) {
Zotero.debug("Translate: Adding " + toAdd, 5);
yield newCollection.addItems(toAdd);
}
}
return topLevelCollection;
}.bind(this));
return topLevelCollections;
}),
/**
* Deletes irrelevant fields from an item object to avoid warnings in Item#fromJSON
* Also delete some things like dateAdded, dateModified, and path that translators
* should not be able to set directly.
*/
"_deleteIrrelevantFields": function(item) {
const DELETE_FIELDS = ["attachments", "notes", "dateAdded", "dateModified", "seeAlso", "version", "id", "itemID", "path"];
for (let i=0; i<DELETE_FIELDS.length; i++) delete item[DELETE_FIELDS[i]];
return item;
},
/**
* Saves a translator attachment to the database
@ -220,44 +226,38 @@ Zotero.Translate.ItemSaver.prototype = {
* @return {Zotero.Primise<Zotero.Item|False} Flase is returned if attachment
* was not saved due to error or user settings.
*/
"_saveAttachment": function(attachment, parentID, attachmentCallback) {
// determine whether to save files and attachments
let attachmentPromise;
if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD) {
attachmentPromise = this._saveAttachmentDownload.apply(this, arguments);
} else if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE) {
attachmentPromise = this._saveAttachmentFile.apply(this, arguments);
} else {
Zotero.debug('Translate: Ignoring attachment due to ATTACHMENT_MODE_IGNORE');
return Zotero.Promise.resolve(false);
}
return attachmentPromise
.then(function(attachmentItem) {
if (!attachmentItem) return false; // attachmentCallback should not have been called in this case
// save fields
attachment.itemType = "attachment";
this._saveFields(attachment, attachmentItem);
// add note if necessary
if(attachment.note) {
attachmentItem.setNote(attachment.note);
"_saveAttachment": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
try {
let newAttachment;
// determine whether to save files and attachments
if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD) {
newAttachment = yield this._saveAttachmentDownload.apply(this, arguments);
} else if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE) {
newAttachment = yield this._saveAttachmentFile.apply(this, arguments);
} else {
Zotero.debug('Translate: Ignoring attachment due to ATTACHMENT_MODE_IGNORE');
return false;
}
return attachmentItem.save()
.then(function() {
Zotero.debug("Translate: Created attachment; id is " + attachmentItem.id, 4);
attachmentCallback(attachment, 100);
return attachmentItem;
})
}.bind(this))
.catch(function(e) {
if (!newAttachment) return false; // attachmentCallback should not have been called in this case
// save fields
if (attachment.accessDate) newAttachment.setField("accessDate", attachment.accessDate);
if (attachment.tags) newAttachment.setTags(this._cleanTags(attachment.tags));
if (attachment.note) newAttachment.setNote(attachment.note);
this._handleRelated(attachment, newAttachment);
yield newAttachment.saveTx();
Zotero.debug("Translate: Created attachment; id is " + newAttachment.id, 4);
attachmentCallback(attachment, 100);
return newAttachment;
} catch(e) {
Zotero.debug(e, 2);
attachmentCallback(attachment, false, e);
return false;
});
},
}
}),
"_saveAttachmentFile": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
Zotero.debug("Translate: Adding attachment", 4);
@ -278,60 +278,39 @@ Zotero.Translate.ItemSaver.prototype = {
}
}
let done = false,
newItem;
if (attachment.path) {
var file = this._parsePath(attachment.path);
if(!file) {
let newItem;
var file = attachment.path && this._parsePath(attachment.path);
if (!file) {
if (attachment.path) {
let asUrl = Zotero.Attachments.cleanAttachmentURI(attachment.path);
if (!attachment.url && !asUrl) {
throw new Error("Translate: Could not parse attachment path <" + attachment.path + ">");
} else if (!attachment.url && asUrl) {
}
if (!attachment.url && asUrl) {
Zotero.debug("Translate: attachment path looks like a URI: " + attachment.path);
attachment.url = asUrl;
delete attachment.path;
}
} else {
if (attachment.url) {
attachment.linkMode = "imported_url";
newItem = yield Zotero.Attachments.importSnapshotFromFile({
file: file,
url: attachment.url,
title: attachment.title,
contentType: attachment.mimeType,
charset: attachment.charset,
parentItemID: parentID
});
}
else {
attachment.linkMode = "imported_file";
newItem = yield Zotero.Attachments.importFromFile({
file: file,
parentItemID: parentID
});
}
done = true;
}
}
if(!done) {
let url = Zotero.Attachments.cleanAttachmentURI(attachment.url);
if (!url) {
throw new Error("Translate: Invalid attachment.url specified <" + attachment.url + ">");
}
attachment.url = url;
url = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newURI(url, null, null); // This cannot fail, since we check above
// see if this is actually a file URL
if(url.scheme == "file") {
throw new Error("Translate: Local file attachments cannot be specified in attachment.url");
} else if(url.scheme != "http" && url.scheme != "https") {
throw new Error("Translate: " + url.scheme + " protocol is not allowed for attachments from translators.");
}
// At this point, must be a valid HTTP/HTTPS url
attachment.linkMode = "linked_file";
newItem = yield Zotero.Attachments.linkFromURL({
@ -339,7 +318,27 @@ Zotero.Translate.ItemSaver.prototype = {
parentItemID: parentID,
contentType: attachment.mimeType || undefined,
title: attachment.title || undefined
})
});
} else {
if (attachment.url) {
attachment.linkMode = "imported_url";
newItem = yield Zotero.Attachments.importSnapshotFromFile({
file: file,
url: attachment.url,
title: attachment.title,
contentType: attachment.mimeType,
charset: attachment.charset,
parentItemID: parentID
});
}
else {
attachment.linkMode = "imported_file";
newItem = yield Zotero.Attachments.importFromFile({
file: file,
parentItemID: parentID
});
if (attachment.title) newItem.setField("title", attachment.title);
}
}
return newItem;
@ -562,8 +561,11 @@ Zotero.Translate.ItemSaver.prototype = {
// Import from URL
let mimeType = attachment.mimeType ? attachment.mimeType : null;
let parentItem = yield Zotero.Items.getAsync(parentID);
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
let fileBaseName;
if (parentID) {
let parentItem = yield Zotero.Items.getAsync(parentID);
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
}
Zotero.debug('Importing attachment from URL');
attachment.linkMode = "imported_url";
@ -578,125 +580,61 @@ Zotero.Translate.ItemSaver.prototype = {
fileBaseName: fileBaseName,
contentType: mimeType,
cookieSandbox: this._cookieSandbox
})
});
}),
"_saveFields":function(item, newItem) {
// fields that should be handled differently
const skipFields = ["note", "notes", "itemID", "attachments", "tags", "seeAlso",
"itemType", "complete", "creators"];
var typeID = Zotero.ItemTypes.getID(item.itemType);
var fieldID;
for(var field in item) {
// loop through item fields
if(item[field] && skipFields.indexOf(field) === -1 && (fieldID = Zotero.ItemFields.getID(field))) {
// if field is in db and shouldn't be skipped
// try to map from base field
if(Zotero.ItemFields.isBaseField(fieldID)) {
fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID);
// Skip mapping if item field already exists
var fieldName = Zotero.ItemFields.getName(fieldID);
if(fieldName !== field && item[fieldName]) continue;
if(fieldID) {
Zotero.debug("Translate: Mapping "+field+" to "+fieldName, 5);
}
}
// if field is valid for this type, set field
if(fieldID && Zotero.ItemFields.isValidForType(fieldID, typeID)) {
newItem.setField(fieldID, item[field]);
} else {
Zotero.debug("Translate: Discarded field "+field+" for item: field not valid for type "+item.itemType, 3);
}
}
"_saveNote":Zotero.Promise.coroutine(function* (note, parentID) {
var myNote = new Zotero.Item('note');
myNote.libraryID = this._libraryID;
if(parentID) {
myNote.parentID = parentID;
}
if(typeof note == "object") {
myNote.setNote(note.note);
if(note.tags) myNote.setTags(this._cleanTags(note.tags));
this._handleRelated(note, myNote);
} else {
myNote.setNote(note);
}
yield myNote.save();
return myNote;
}),
/**
* Remove automatic tags if automatic tags pref is on, and set type
* to automatic if forced
*/
"_cleanTags":function(tags) {
// If all tags are automatic and automatic tags pref is on, return immediately
let tagPref = Zotero.Prefs.get("automaticTags");
if(this._forceTagType == 1 && !tagPref) return [];
let newTags = [];
for(let i=0; i<tags.length; i++) {
let tag = tags[i];
tag.type = this._forceTagType || tag.type || 0;
newTags.push(tag);
}
return newTags;
},
"_saveNotes":function(item, parentID) {
for(var i=0; i<item.notes.length; i++) {
var note = item.notes[i];
if(!note) continue;
var myNote = new Zotero.Item('note');
myNote.libraryID = this._libraryID;
myNote.setNote(typeof note == "object" ? note.note : note);
if(parentID) {
myNote.parentID = parentID;
}
var noteID = myNote.save();
if(typeof note == "object") {
// handle see also
myNote = Zotero.Items.get(noteID);
this._saveTags(note, myNote);
}
}
},
"_saveTags":function(item, newItem) {
"_handleRelated":function(item, newItem) {
// add to ID map
if(item.itemID) {
this._IDMap[item.itemID] = newItem.id;
}
// add see alsos
if(item.seeAlso) {
for(var i=0; i<item.seeAlso.length; i++) {
var seeAlso = item.seeAlso[i];
if(this._IDMap[seeAlso]) {
newItem.addRelatedItem(this._IDMap[seeAlso]);
}
}
newItem.save();
}
// if all tags are automatic and automatic tags pref is on, return immediately
var tagPref = Zotero.Prefs.get("automaticTags");
if(this._forceTagType == 1 && !tagPref) return;
// add tags
if(item.tags) {
var tagsToAdd = {};
tagsToAdd[0] = []; // user tags
tagsToAdd[1] = []; // automatic tags
for(var i=0; i<item.tags.length; i++) {
var tag = item.tags[i];
if(typeof(tag) == "string") {
// accept strings in tag array as automatic tags, or, if
// importing, as non-automatic tags
if(this._forceTagType) {
tagsToAdd[this._forceTagType].push(tag);
} else {
tagsToAdd[0].push(tag);
}
} else if(typeof(tag) == "object") {
// also accept objects
if(tag.tag || tag.name) {
if(this._forceTagType) {
var tagType = this._forceTagType;
} else if(tag.type) {
// skip automatic tags during import too (?)
if(tag.type == 1 && !tagPref) continue;
var tagType = tag.type;
} else {
var tagType = 0;
}
tagsToAdd[tagType].push(tag.tag ? tag.tag : tag.name);
}
}
}
for (var type in [0, 1]) {
if (tagsToAdd[type].length) {
newItem.addTags(tagsToAdd[type], type);
}
}
if(item.itemID || item.id) {
this._IDMap[item.itemID || item.id] = newItem.id;
}
// // add see alsos
// if(item.seeAlso) {
// for(var i=0; i<item.seeAlso.length; i++) {
// var seeAlso = item.seeAlso[i];
// if(this._IDMap[seeAlso]) {
// newItem.addRelatedItem(this._IDMap[seeAlso]);
// }
// }
// newItem.save();
// }
}
}
@ -863,7 +801,7 @@ Zotero.Translate.ItemGetter.prototype = {
// The only attachments that can have multiple supporting files are imported
// attachments of mime type text/html (specified in Attachments.getNumFiles())
if(attachment.attachmentMIMEType == "text/html"
if(attachment.attachmentContentType == "text/html"
&& linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE
&& Zotero.Attachments.getNumFiles(attachment) > 1) {
// Attachment is a snapshot with supporting files. Check if any of the

View file

@ -105,6 +105,7 @@ user_pref("extensions.zotero.debug.time", $DEBUG);
user_pref("extensions.zotero.firstRunGuidance", false);
user_pref("extensions.zotero.firstRun2", false);
user_pref("extensions.zotero.reportTranslationFailure", false);
user_pref("extensions.zotero.httpServer.enabled", true);
EOF
# -v flag on Windows makes Firefox process hang

View file

@ -0,0 +1,16 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<meta name="wpd_version" content="0.2">
<meta name="wpd_baseurl" content="http://209.141.35.188/">
<meta name="wpd_url" content="http://209.141.35.188/">
<meta name="wpd_date" content="2015-05-05T00:06Z">
</head>
<body><h1>It works!</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added, yet.</p>
</body>
</html>

View file

@ -1,5 +1,551 @@
new function() {
Components.utils.import("resource://gre/modules/osfile.jsm");
/**
* Build a dummy translator that can be passed to Zotero.Translate
*/
function buildDummyTranslator(translatorType, code) {
let info = {
"translatorID":"dummy-translator",
"translatorType":1, // import
"label":"Dummy Translator",
"creator":"Simon Kornblith",
"target":"",
"priority":100,
"browserSupport":"g",
"inRepository":false,
"lastUpdated":"0000-00-00 00:00:00",
};
let translator = new Zotero.Translator(info);
translator.code = code;
return translator;
}
/**
* Create a new translator that saves the specified items
* @param {String} translatorType - "import" or "web"
* @param {Object} items - items as translator JSON
*/
function saveItemsThroughTranslator(translatorType, items) {
let tyname;
if (translatorType == "web") {
tyname = "Web";
} else if (translatorType == "import") {
tyname = "Import";
} else {
throw new Error("invalid translator type "+translatorType);
}
let translate = new Zotero.Translate[tyname]();
let browser;
if (translatorType == "web") {
browser = Zotero.Browser.createHiddenBrowser();
translate.setDocument(browser.contentDocument);
} else if (translatorType == "import") {
translate.setString("");
}
translate.setTranslator(buildDummyTranslator(translatorType == "web" ? 4 : 1,
"function detectWeb() {}\n"+
"function do"+tyname+"() {\n"+
" var json = JSON.parse('"+JSON.stringify(items).replace(/'/g, "\'")+"');\n"+
" for (var i=0; i<json.length; i++) {"+
" var item = new Zotero.Item;\n"+
" for (var field in json[i]) { item[field] = json[i][field]; }\n"+
" item.complete();\n"+
" }\n"+
"}"));
return translate.translate().then(function(items) {
if (browser) Zotero.Browser.deleteHiddenBrowser(browser);
return items;
});
}
/**
* Convert an array of items to an object in which they are indexed by
* their display titles
*/
var itemsArrayToObject = Zotero.Promise.coroutine(function* itemsArrayToObject(items) {
var obj = {};
for (let item of items) {
obj[yield item.loadDisplayTitle(true)] = item;
}
return obj;
});
const TEST_TAGS = [
"manual tag as string",
{"tag":"manual tag as object"},
{"tag":"manual tag as object with type", "type":0},
{"tag":"automatic tag as object", "type":1},
{"name":"tag in name property"}
];
/**
* Check that tags match expected values, if TEST_TAGS is passed as test array
*/
function checkTestTags(newItem, web) {
assert.equal(newItem.getTagType("manual tag as string"), web ? 1 : 0);
assert.equal(newItem.getTagType("manual tag as object"), web ? 1 : 0);
assert.equal(newItem.getTagType("manual tag as object with type"), web ? 1 : 0);
assert.equal(newItem.getTagType("automatic tag as object"), 1);
assert.equal(newItem.getTagType("tag in name property"), web ? 1 : 0);
}
/**
* Get included test snapshot file
* @returns {nsIFile}
*/
function getTestSnapshot() {
let snapshot = getTestDataDirectory();
snapshot.append("snapshot");
snapshot.append("index.html");
return snapshot;
}
/**
* Get included test snapshot file
* @returns {nsIFile}
*/
function getTestPDF() {
let testPDF = getTestDataDirectory();
testPDF.append("empty.pdf");
return testPDF;
}
/**
* Set up endpoints for testing attachment saving
* This must happen immediately before the test, since Zotero might get
* restarted by resetDB(), which would erase our registered endpoints.
*/
function setupAttachmentEndpoints() {
var SnapshotTest = function() {};
Zotero.Server.Endpoints["/test/translate/test.html"] = SnapshotTest;
SnapshotTest.prototype = {
"supportedMethods":["GET"],
"init":function(data, sendResponseCallback) {
Zotero.File.getBinaryContentsAsync(getTestSnapshot()).then(function (data) {
sendResponseCallback(200, "text/html", data);
});
}
}
var PDFTest = function() {};
Zotero.Server.Endpoints["/test/translate/test.pdf"] = PDFTest;
PDFTest.prototype = {
"supportedMethods":["GET"],
"init":function(data, sendResponseCallback) {
Zotero.File.getBinaryContentsAsync(getTestPDF()).then(function (data) {
sendResponseCallback(200, "application/pdf", data);
});
}
}
var NonExistentTest = function() {};
Zotero.Server.Endpoints["/test/translate/does_not_exist.html"] = NonExistentTest;
NonExistentTest.prototype = {
"supportedMethods":["GET"],
"init":function(data, sendResponseCallback) {
sendResponseCallback(404, "text/html", "File does not exist");
}
}
}
describe("Zotero.Translate", function() {
let win;
before(function* () {
setupAttachmentEndpoints();
win = yield loadBrowserWindow();
});
after(function () {
win.close();
});
describe("Zotero.Item", function() {
it('should save ordinary fields and creators', function* () {
let data = loadSampleData('allTypesAndFields');
let trueItems = loadSampleData('itemJSON');
let saveItems = [];
for (let itemType in data) {
saveItems.push(data[itemType]);
let trueItem = trueItems[itemType];
delete trueItem.dateAdded;
delete trueItem.dateModified;
delete trueItem.key;
}
let newItems = yield saveItemsThroughTranslator("import", saveItems);
let savedItems = {};
for (let i=0; i<newItems.length; i++) {
let savedItem = yield newItems[i].toJSON();
savedItems[Zotero.ItemTypes.getName(newItems[i].itemTypeID)] = savedItem;
delete savedItem.dateAdded;
delete savedItem.dateModified;
delete savedItem.key;
}
assert.deepEqual(savedItems, trueItems, "saved items match inputs");
});
it('should save tags', function* () {
let myItem = {
"itemType":"book",
"title":"Test Item",
"tags":TEST_TAGS
};
checkTestTags((yield saveItemsThroughTranslator("import", [myItem]))[0]);
});
it('should save notes', function* () {
let myItems = [
{
"itemType":"book",
"title":"Test Item",
"notes":[
"1 note as string",
{
"note":"2 note as object",
"tags":TEST_TAGS
}
]
},
{
"itemType":"note",
"note":"standalone note",
"tags":TEST_TAGS
}
];
let newItems = yield itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
let noteIDs = newItems["Test Item"].getNotes();
let note1 = yield Zotero.Items.getAsync(noteIDs[0]);
assert.equal(Zotero.ItemTypes.getName(note1.itemTypeID), "note");
assert.equal(note1.getNote(), "1 note as string");
let note2 = yield Zotero.Items.getAsync(noteIDs[1]);
assert.equal(Zotero.ItemTypes.getName(note2.itemTypeID), "note");
assert.equal(note2.getNote(), "2 note as object");
checkTestTags(note2);
let note3 = newItems["standalone note"];
assert.equal(note3.getNote(), "standalone note");
checkTestTags(note3);
});
it('should save collections', function* () {
let translate = new Zotero.Translate.Import();
translate.setString("");
translate.setTranslator(buildDummyTranslator(4,
'function detectWeb() {}\n'+
'function doImport() {\n'+
' var item1 = new Zotero.Item("book");\n'+
' item1.title = "Not in Collection";\n'+
' item1.complete();\n'+
' var item2 = new Zotero.Item("book");\n'+
' item2.id = 1;\n'+
' item2.title = "In Parent Collection";\n'+
' item2.complete();\n'+
' var item3 = new Zotero.Item("book");\n'+
' item3.id = 2;\n'+
' item3.title = "In Child Collection";\n'+
' item3.complete();\n'+
' var collection = new Zotero.Collection();\n'+
' collection.name = "Parent Collection";\n'+
' collection.children = [{"id":1}, {"type":"collection", "name":"Child Collection", "children":[{"id":2}]}];\n'+
' collection.complete();\n'+
'}'));
let newItems = yield translate.translate();
assert.equal(newItems.length, 3);
newItems = yield itemsArrayToObject(newItems);
assert.equal(newItems["Not in Collection"].getCollections().length, 0);
let parentCollection = newItems["In Parent Collection"].getCollections();
assert.equal(parentCollection.length, 1);
parentCollection = (yield Zotero.Collections.getAsync(parentCollection))[0];
assert.equal(parentCollection.name, "Parent Collection");
assert.isTrue(parentCollection.hasChildCollections());
let childCollection = newItems["In Child Collection"].getCollections();
assert.equal(childCollection.length, 1);
childCollection = (yield Zotero.Collections.getAsync(childCollection[0]));
assert.equal(childCollection.name, "Child Collection");
let parentChildren = parentCollection.getChildCollections();
assert.equal(parentChildren.length, 1);
assert.equal(parentChildren[0], childCollection);
});
it('import translators should save attachments', function* () {
let emptyPDF = getTestPDF().path;
let snapshot = getTestSnapshot().path;
let myItems = [
{
"itemType":"attachment",
"path":emptyPDF,
"title":"Empty PDF",
"note":"attachment note",
"tags":TEST_TAGS
},
{
"itemType":"attachment",
"url":"http://www.zotero.org/",
"title":"Link to zotero.org",
"note":"attachment 2 note",
"tags":TEST_TAGS
}
];
let childAttachments = myItems.slice();
childAttachments.push({
"itemType":"attachment",
"path":snapshot,
"url":"http://www.example.com/",
"title":"Snapshot",
"note":"attachment 3 note",
"tags":TEST_TAGS
});
myItems.push({
"itemType":"book",
"title":"Container Item",
"attachments":childAttachments
});
let newItems = yield itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
let containedAttachments = yield Zotero.Items.getAsync(newItems["Container Item"].getAttachments());
assert.equal(containedAttachments.length, 3);
for (let savedAttachments of [[newItems["Empty PDF"], newItems["Link to zotero.org"]],
[containedAttachments[0], containedAttachments[1]]]) {
assert.equal(savedAttachments[0].getField("title"), "Empty PDF");
assert.equal(savedAttachments[0].getNote(), "attachment note");
assert.equal(savedAttachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_FILE);
checkTestTags(savedAttachments[0]);
assert.equal(savedAttachments[1].getField("title"), "Link to zotero.org");
assert.equal(savedAttachments[1].getField("url"), "http://www.zotero.org/");
assert.equal(savedAttachments[1].getNote(), "attachment 2 note");
assert.equal(savedAttachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
checkTestTags(savedAttachments[1]);
}
assert.equal(containedAttachments[2].getField("title"), "Snapshot");
assert.equal(containedAttachments[2].getField("url"), "http://www.example.com/");
assert.equal(containedAttachments[2].getNote(), "attachment 3 note");
assert.equal(containedAttachments[2].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
checkTestTags(containedAttachments[2]);
});
it('import translators should save missing snapshots as links', function* () {
let missingFile = getTestDataDirectory();
missingFile.append("missing");
assert.isFalse(missingFile.exists());
missingFile = missingFile.path;
let myItems = [
{
"itemType":"book",
"title":"Container Item",
"attachments":[
{
"itemType":"attachment",
"path":missingFile,
"url":"http://www.example.com/",
"title":"Snapshot with missing file",
"note":"attachment note",
"tags":TEST_TAGS
}
]
}
];
let newItems = yield saveItemsThroughTranslator("import", myItems);
assert.equal(newItems.length, 1);
assert.equal(newItems[0].getField("title"), "Container Item");
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
assert.equal(containedAttachments.length, 1);
assert.equal(containedAttachments[0].getField("title"), "Snapshot with missing file");
assert.equal(containedAttachments[0].getField("url"), "http://www.example.com/");
assert.equal(containedAttachments[0].getNote(), "attachment note");
assert.equal(containedAttachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
checkTestTags(containedAttachments[0]);
});
it('import translators should ignore missing file attachments', function* () {
let missingFile = getTestDataDirectory();
missingFile.append("missing");
assert.isFalse(missingFile.exists());
missingFile = missingFile.path;
let myItems = [
{
"itemType":"attachment",
"path":missingFile,
"title":"Missing file"
},
{
"itemType":"book",
"title":"Container Item",
"attachments":[
{
"itemType":"attachment",
"path":missingFile,
"title":"Missing file"
}
]
}
];
let newItems = yield saveItemsThroughTranslator("import", myItems);
assert.equal(newItems.length, 1);
assert.equal(newItems[0].getField("title"), "Container Item");
assert.equal(newItems[0].getAttachments().length, 0);
});
it('web translators should save attachments', function* () {
let myItems = [
{
"itemType":"book",
"title":"Container Item",
"attachments":[
{
"url":"http://www.zotero.org/",
"title":"Link to zotero.org",
"note":"attachment note",
"tags":TEST_TAGS,
"snapshot":false
},
{
"url":"http://127.0.0.1:23119/test/translate/test.html",
"title":"Test Snapshot",
"note":"attachment 2 note",
"tags":TEST_TAGS
},
{
"url":"http://127.0.0.1:23119/test/translate/test.pdf",
"title":"Test PDF",
"note":"attachment 3 note",
"tags":TEST_TAGS
}
]
}
];
let newItems = yield saveItemsThroughTranslator("web", myItems);
assert.equal(newItems.length, 1);
let containedAttachments = yield itemsArrayToObject(yield Zotero.Items.getAsync(newItems[0].getAttachments()));
let link = containedAttachments["Link to zotero.org"];
assert.equal(link.getField("url"), "http://www.zotero.org/");
assert.equal(link.getNote(), "attachment note");
assert.equal(link.attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
checkTestTags(link, true);
let snapshot = containedAttachments["Test Snapshot"];
assert.equal(snapshot.getField("url"), "http://127.0.0.1:23119/test/translate/test.html");
assert.equal(snapshot.getNote(), "attachment 2 note");
assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
assert.equal(snapshot.attachmentContentType, "text/html");
checkTestTags(snapshot, true);
let pdf = containedAttachments["Test PDF"];
assert.equal(pdf.getField("url"), "http://127.0.0.1:23119/test/translate/test.pdf");
assert.equal(pdf.getNote(), "attachment 3 note");
assert.equal(pdf.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
assert.equal(pdf.attachmentContentType, "application/pdf");
checkTestTags(pdf, true);
});
it('web translators should save attachment from document', function* () {
let deferred = Zotero.Promise.defer();
let browser = Zotero.HTTP.processDocuments("http://127.0.0.1:23119/test/translate/test.html",
function (doc) { deferred.resolve(doc) }, undefined,
undefined, true);
let doc = yield deferred.promise;
let translate = new Zotero.Translate.Web();
translate.setDocument(doc);
translate.setTranslator(buildDummyTranslator(4,
'function detectWeb() {}\n'+
'function doWeb(doc) {\n'+
' var item = new Zotero.Item("book");\n'+
' item.title = "Container Item";\n'+
' item.attachments = [{\n'+
' "document":doc,\n'+
' "title":"Snapshot from Document",\n'+
' "note":"attachment note",\n'+
' "tags":'+JSON.stringify(TEST_TAGS)+'\n'+
' }];\n'+
' item.complete();\n'+
'}'));
let newItems = yield translate.translate();
assert.equal(newItems.length, 1);
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
assert.equal(containedAttachments.length, 1);
let snapshot = containedAttachments[0];
assert.equal(snapshot.getField("url"), "http://127.0.0.1:23119/test/translate/test.html");
assert.equal(snapshot.getNote(), "attachment note");
assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
assert.equal(snapshot.attachmentContentType, "text/html");
checkTestTags(snapshot, true);
Zotero.Browser.deleteHiddenBrowser(browser);
});
it('web translators should ignore attachments that return error codes', function* () {
this.timeout(60000);
let myItems = [
{
"itemType":"book",
"title":"Container Item",
"attachments":[
{
"url":"http://127.0.0.1:23119/test/translate/does_not_exist.html",
"title":"Non-Existent HTML"
},
{
"url":"http://127.0.0.1:23119/test/translate/does_not_exist.pdf",
"title":"Non-Existent PDF"
}
]
}
];
let newItems = yield saveItemsThroughTranslator("web", myItems);
assert.equal(newItems.length, 1);
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
assert.equal(containedAttachments.length, 0);
});
it('web translators should save PDFs only if the content type matches', function* () {
this.timeout(60000);
let myItems = [
{
"itemType":"book",
"title":"Container Item",
"attachments":[
{
"url":"http://127.0.0.1:23119/test/translate/test.html",
"mimeType":"application/pdf",
"title":"Test PDF with wrong mime type"
},
{
"url":"http://127.0.0.1:23119/test/translate/test.pdf",
"mimeType":"application/pdf",
"title":"Test PDF",
"note":"attachment note",
"tags":TEST_TAGS
}
]
}
];
let newItems = yield saveItemsThroughTranslator("web", myItems);
assert.equal(newItems.length, 1);
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
assert.equal(containedAttachments.length, 1);
let pdf = containedAttachments[0];
assert.equal(pdf.getField("title"), "Test PDF");
assert.equal(pdf.getField("url"), "http://127.0.0.1:23119/test/translate/test.pdf");
assert.equal(pdf.getNote(), "attachment note");
assert.equal(pdf.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
checkTestTags(pdf, true);
});
});
});
describe("Zotero.Translate.ItemGetter", function() {
describe("nextItem", function() {
it('should return false for an empty database', Zotero.Promise.coroutine(function* () {
@ -380,9 +926,8 @@ describe("Zotero.Translate.ItemGetter", function() {
it('should return stored/linked file and URI attachments in expected format', Zotero.Promise.coroutine(function* () {
this.timeout(60000);
let file = getTestDataDirectory();
let file = getTestPDF();
let item, relatedItem;
file.append("empty.pdf");
yield Zotero.DB.executeTransaction(function* () {
item = new Zotero.Item('journalArticle');
@ -587,4 +1132,5 @@ describe("Zotero.Translate.ItemGetter", function() {
}
}));
});
});
});
}