7c25093ca2
As noted in 27cb099c82
, import translators should be rewritten to return
a promise from doImport() and wait for promises from successive
item.complete() calls. They should then be marked as minVersion: "5.0"
to be handled properly by this new code.
(But this tries to account, albeit with somewhat worse behavior, for
translators that haven't been rewritten and sandboxes without Promise
(which is currently the case with child sandboxes in the client).)
(Oh, and I haven't tested this at all in the connectors.)
1548 lines
No EOL
59 KiB
JavaScript
1548 lines
No EOL
59 KiB
JavaScript
new function() {
|
|
Components.utils.import("resource://gre/modules/osfile.jsm");
|
|
|
|
/**
|
|
* 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,
|
|
"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
|
|
*/
|
|
function itemsArrayToObject(items) {
|
|
var obj = {};
|
|
for (let item of items) {
|
|
obj[item.getDisplayTitle()] = 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* () {
|
|
// TEMP: Fix for slow translator initialization on Linux/Travis
|
|
this.timeout(20000);
|
|
yield Zotero.Translators.init();
|
|
|
|
setupAttachmentEndpoints();
|
|
win = yield loadBrowserWindow();
|
|
});
|
|
after(function () {
|
|
win.close();
|
|
});
|
|
|
|
describe("Zotero.Item", function() {
|
|
it('should save ordinary fields and creators', function* () {
|
|
this.timeout(10000);
|
|
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 = 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 accept deprecated SQL accessDates', function* () {
|
|
let myItem = {
|
|
"itemType":"webpage",
|
|
"title":"Test Item",
|
|
"accessDate":"2015-01-02 03:04:05"
|
|
}
|
|
let newItems = yield saveItemsThroughTranslator("import", [myItem]);
|
|
assert.equal(newItems[0].getField("accessDate"), "2015-01-02 03:04:05");
|
|
});
|
|
|
|
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 = 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 = 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 = 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 set accessDate to current date', function* () {
|
|
let myItem = {
|
|
"itemType":"webpage",
|
|
"title":"Test Item",
|
|
"url":"http://www.zotero.org/"
|
|
};
|
|
let newItems = yield saveItemsThroughTranslator("web", [myItem]);
|
|
let currentDate = new Date();
|
|
let delta = currentDate - Zotero.Date.sqlToDate(newItems[0].getField("accessDate"), true);
|
|
assert.isAbove(delta, -500);
|
|
assert.isBelow(delta, 5000);
|
|
});
|
|
|
|
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 = 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);
|
|
});
|
|
|
|
it('should not convert tags to canonical form in child translators', function* () {
|
|
var childTranslator = buildDummyTranslator(1,
|
|
`function detectWeb() {}
|
|
function doImport() {
|
|
var item = new Zotero.Item;
|
|
item.itemType = "book";
|
|
item.title = "The Definitive Guide of Owls";
|
|
item.tags = ['owl', 'tag'];
|
|
item.complete();
|
|
}`, {translatorID: 'child-dummy-translator'}
|
|
);
|
|
sinon.stub(Zotero.Translators, 'get').withArgs('child-dummy-translator').returns(childTranslator);
|
|
|
|
var parentTranslator = buildDummyTranslator(1,
|
|
`function detectWeb() {}
|
|
function doImport() {
|
|
var translator = Zotero.loadTranslator("import");
|
|
translator.setTranslator('child-dummy-translator');
|
|
translator.setHandler('itemDone', Zotero.childItemDone);
|
|
translator.translate();
|
|
}`
|
|
);
|
|
|
|
function childItemDone(obj, item) {
|
|
// Non-canonical tags after child translator is done
|
|
assert.deepEqual(['owl', 'tag'], item.tags);
|
|
item.complete();
|
|
}
|
|
|
|
var translate = new Zotero.Translate.Import();
|
|
translate.setTranslator(parentTranslator);
|
|
translate.setString("");
|
|
yield translate._loadTranslator(parentTranslator);
|
|
translate._sandboxManager.importObject({childItemDone});
|
|
|
|
var items = yield translate.translate();
|
|
|
|
// Canonicalized tags after parent translator
|
|
assert.deepEqual([{tag: 'owl'}, {tag: 'tag'}], items[0].getTags());
|
|
|
|
Zotero.Translators.get.restore();
|
|
});
|
|
});
|
|
|
|
|
|
describe("Translators", function () {
|
|
it("should round-trip child attachment via BibTeX", function* () {
|
|
var item = yield createDataObject('item');
|
|
yield importFileAttachment('test.png', { parentItemID: item.id });
|
|
|
|
var translation = new Zotero.Translate.Export();
|
|
var tmpDir = yield getTempDirectory();
|
|
var exportDir = OS.Path.join(tmpDir, 'export');
|
|
translation.setLocation(Zotero.File.pathToFile(exportDir));
|
|
translation.setItems([item]);
|
|
translation.setTranslator("9cb70025-a888-4a29-a210-93ec52da40d4");
|
|
translation.setDisplayOptions({
|
|
exportFileData: true
|
|
});
|
|
yield translation.translate();
|
|
|
|
var exportFile = OS.Path.join(exportDir, 'export.bib');
|
|
assert.isTrue(yield OS.File.exists(exportFile));
|
|
|
|
var translation = new Zotero.Translate.Import();
|
|
translation.setLocation(Zotero.File.pathToFile(exportFile));
|
|
var translators = yield translation.getTranslators();
|
|
translation.setTranslator(translators[0]);
|
|
var importCollection = yield createDataObject('collection');
|
|
var items = yield translation.translate({
|
|
libraryID: Zotero.Libraries.userLibraryID,
|
|
collections: [importCollection.id]
|
|
});
|
|
|
|
assert.lengthOf(items, 1);
|
|
var attachments = items[0].getAttachments();
|
|
assert.lengthOf(attachments, 1);
|
|
var attachment = Zotero.Items.get(attachments[0]);
|
|
assert.isTrue(yield attachment.fileExists());
|
|
});
|
|
});
|
|
|
|
|
|
describe("ItemSaver", function () {
|
|
describe("#saveCollections()", function () {
|
|
it("should add top-level collections to specified collection", function* () {
|
|
var collection = yield createDataObject('collection');
|
|
var collections = [
|
|
{
|
|
name: "Collection",
|
|
type: "collection",
|
|
children: []
|
|
}
|
|
];
|
|
var items = [
|
|
{
|
|
itemType: "book",
|
|
title: "Test"
|
|
}
|
|
];
|
|
|
|
var translation = new Zotero.Translate.Import();
|
|
translation.setString("");
|
|
translation.setTranslator(buildDummyTranslator(
|
|
"import",
|
|
"function detectImport() {}\n"
|
|
+ "function doImport() {\n"
|
|
+ " var json = JSON.parse('" + JSON.stringify(collections).replace(/['\\]/g, "\\$&") + "');\n"
|
|
+ " for (let o of json) {"
|
|
+ " var collection = new Zotero.Collection;\n"
|
|
+ " for (let field in o) { collection[field] = o[field]; }\n"
|
|
+ " collection.complete();\n"
|
|
+ " }\n"
|
|
+ " json = JSON.parse('" + JSON.stringify(items).replace(/['\\]/g, "\\$&") + "');\n"
|
|
+ " for (let o of json) {"
|
|
+ " var item = new Zotero.Item;\n"
|
|
+ " for (let field in o) { item[field] = o[field]; }\n"
|
|
+ " item.complete();\n"
|
|
+ " }\n"
|
|
+ "}"
|
|
));
|
|
yield translation.translate({
|
|
collections: [collection.id]
|
|
});
|
|
assert.lengthOf(translation.newCollections, 1);
|
|
assert.isNumber(translation.newCollections[0].id);
|
|
assert.lengthOf(translation.newItems, 1);
|
|
assert.isNumber(translation.newItems[0].id);
|
|
var childCollections = Array.from(collection.getChildCollections(true));
|
|
assert.sameMembers(childCollections, translation.newCollections.map(c => c.id));
|
|
});
|
|
});
|
|
|
|
describe("#_saveAttachment()", function () {
|
|
it("should save standalone attachment to collection", function* () {
|
|
var collection = yield createDataObject('collection');
|
|
var items = [
|
|
{
|
|
itemType: "attachment",
|
|
title: "Test",
|
|
mimeType: "text/html",
|
|
url: "http://example.com"
|
|
}
|
|
];
|
|
|
|
var translation = new Zotero.Translate.Import();
|
|
translation.setString("");
|
|
translation.setTranslator(buildDummyTranslator(
|
|
"import",
|
|
"function detectImport() {}\n"
|
|
+ "function doImport() {\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"
|
|
+ "}"
|
|
));
|
|
yield translation.translate({
|
|
collections: [collection.id]
|
|
});
|
|
assert.lengthOf(translation.newItems, 1);
|
|
assert.isNumber(translation.newItems[0].id);
|
|
assert.ok(collection.hasItem(translation.newItems[0].id));
|
|
});
|
|
|
|
});
|
|
describe('#saveItems', function() {
|
|
it("should deproxify item and attachment urls when proxy provided", function* (){
|
|
var itemID;
|
|
var item = loadSampleData('journalArticle');
|
|
item = item.journalArticle;
|
|
item.url = 'https://www-example-com.proxy.example.com/';
|
|
item.attachments = [{
|
|
url: 'https://www-example-com.proxy.example.com/pdf.pdf',
|
|
mimeType: 'application/pdf',
|
|
title: 'Example PDF'}];
|
|
var itemSaver = new Zotero.Translate.ItemSaver({
|
|
libraryID: Zotero.Libraries.userLibraryID,
|
|
attachmentMode: Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE,
|
|
proxy: new Zotero.Proxy({scheme: 'https://%h.proxy.example.com/%p', dotsToHyphens: true})
|
|
});
|
|
var itemDeferred = Zotero.Promise.defer();
|
|
var attachmentDeferred = Zotero.Promise.defer();
|
|
itemSaver.saveItems([item], Zotero.Promise.coroutine(function* (attachment, progressPercentage) {
|
|
// ItemSaver returns immediately without waiting for attachments, so we use the callback
|
|
// to test attachments
|
|
if (progressPercentage != 100) return;
|
|
try {
|
|
yield itemDeferred.promise;
|
|
let item = Zotero.Items.get(itemID);
|
|
attachment = Zotero.Items.get(item.getAttachments()[0]);
|
|
assert.equal(attachment.getField('url'), 'https://www.example.com/pdf.pdf');
|
|
attachmentDeferred.resolve();
|
|
} catch (e) {
|
|
attachmentDeferred.reject(e);
|
|
}
|
|
})).then(function(items) {
|
|
try {
|
|
assert.equal(items[0].getField('url'), 'https://www.example.com/');
|
|
itemID = items[0].id;
|
|
itemDeferred.resolve();
|
|
} catch (e) {
|
|
itemDeferred.reject(e);
|
|
}
|
|
});
|
|
yield Zotero.Promise.all([itemDeferred.promise, attachmentDeferred.promise]);
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
describe("Error Handling", function () {
|
|
it("should propagate saveItems() errors from synchronous doImport()", function* () {
|
|
var items = [
|
|
{
|
|
// Invalid object
|
|
},
|
|
{
|
|
itemType: "book",
|
|
title: "B"
|
|
}
|
|
];
|
|
|
|
var added = 0;
|
|
var notifierID = Zotero.Notifier.registerObserver({
|
|
notify: function (event, type, ids, extraData) {
|
|
added++;
|
|
}
|
|
}, ['item']);
|
|
|
|
var translation = new Zotero.Translate.Import();
|
|
translation.setString("");
|
|
translation.setTranslator(buildDummyTranslator(
|
|
"import",
|
|
"function detectImport() {}"
|
|
+ "function doImport() {"
|
|
+ " var json = JSON.parse('" + JSON.stringify(items).replace(/['\\]/g, "\\$&") + "');"
|
|
+ " for (let o of json) {"
|
|
+ " let item = new Zotero.Item;"
|
|
+ " for (let field in o) { item[field] = o[field]; }"
|
|
+ " item.complete();"
|
|
+ " }"
|
|
+ "}"
|
|
));
|
|
var e = yield getPromiseError(translation.translate());
|
|
Zotero.Notifier.unregisterObserver(notifierID);
|
|
assert.ok(e);
|
|
|
|
// Saving should be stopped without any saved items
|
|
assert.equal(added, 0);
|
|
assert.equal(translation._savingItems, 0);
|
|
assert.equal(translation._runningAsyncProcesses, 0);
|
|
assert.isNull(translation._currentState);
|
|
});
|
|
|
|
it("should propagate saveItems() errors from asynchronous doImport()", function* () {
|
|
var items = [
|
|
{
|
|
// Invalid object
|
|
},
|
|
{
|
|
itemType: "book",
|
|
title: "B"
|
|
}
|
|
];
|
|
|
|
var added = 0;
|
|
var notifierID = Zotero.Notifier.registerObserver({
|
|
notify: function (event, type, ids, extraData) {
|
|
added++;
|
|
}
|
|
}, ['item']);
|
|
|
|
var translation = new Zotero.Translate.Import();
|
|
translation.setString("");
|
|
translation.setTranslator(buildDummyTranslator(
|
|
"import",
|
|
"function detectImport() {}"
|
|
+ "function doImport() {"
|
|
+ " var json = JSON.parse('" + JSON.stringify(items).replace(/['\\]/g, "\\$&") + "');"
|
|
+ " return new Promise(function (resolve, reject) {"
|
|
+ " function next() {"
|
|
+ " var data = json.shift();"
|
|
+ " if (!data) {"
|
|
+ " resolve();"
|
|
+ " return;"
|
|
+ " }"
|
|
+ " var item = new Zotero.Item;"
|
|
+ " for (let field in data) { item[field] = data[field]; }"
|
|
+ " item.complete().then(next).catch(reject);"
|
|
+ " }"
|
|
+ " next();"
|
|
+ " });"
|
|
+ "}",
|
|
{
|
|
minVersion: "5.0"
|
|
}
|
|
));
|
|
var e = yield getPromiseError(translation.translate());
|
|
Zotero.Notifier.unregisterObserver(notifierID);
|
|
assert.ok(e);
|
|
|
|
// Saving should be stopped without any saved items
|
|
assert.equal(added, 0);
|
|
assert.equal(translation._savingItems, 0);
|
|
assert.equal(translation._runningAsyncProcesses, 0);
|
|
assert.isNull(translation._currentState);
|
|
});
|
|
|
|
it("should propagate errors from saveItems with synchronous doSearch()", function* () {
|
|
var stub = sinon.stub(Zotero.Translate.ItemSaver.prototype, "saveItems");
|
|
stub.returns(Zotero.Promise.reject(new Error("Save error")));
|
|
|
|
var translation = new Zotero.Translate.Search();
|
|
translation.setTranslator(buildDummyTranslator(
|
|
"search",
|
|
"function detectSearch() {}"
|
|
+ "function doSearch() {"
|
|
+ " var item = new Zotero.Item('journalArticle');"
|
|
+ " item.itemType = 'book';"
|
|
+ " item.title = 'A';"
|
|
+ " item.complete();"
|
|
+ "}"
|
|
));
|
|
translation.setSearch({ itemType: "journalArticle", DOI: "10.111/Test"});
|
|
var e = yield getPromiseError(translation.translate({
|
|
libraryID: Zotero.Libraries.userLibraryID,
|
|
saveAttachments: false
|
|
}));
|
|
assert.ok(e);
|
|
|
|
stub.restore();
|
|
});
|
|
|
|
it("should propagate errors from saveItems() with asynchronous doSearch()", function* () {
|
|
var stub = sinon.stub(Zotero.Translate.ItemSaver.prototype, "saveItems");
|
|
stub.returns(Zotero.Promise.reject(new Error("Save error")));
|
|
|
|
var translation = new Zotero.Translate.Search();
|
|
translation.setTranslator(buildDummyTranslator(
|
|
"search",
|
|
"function detectSearch() {}"
|
|
+ "function doSearch() {"
|
|
+ " var item = new Zotero.Item('journalArticle');"
|
|
+ " item.itemType = 'book';"
|
|
+ " item.title = 'A';"
|
|
+ " return new Promise(function (resolve, reject) {"
|
|
+ " item.complete().then(next).catch(reject);"
|
|
+ " });"
|
|
+ "}",
|
|
{
|
|
minVersion: "5.0"
|
|
}
|
|
));
|
|
translation.setSearch({ itemType: "journalArticle", DOI: "10.111/Test"});
|
|
var e = yield getPromiseError(translation.translate({
|
|
libraryID: Zotero.Libraries.userLibraryID,
|
|
saveAttachments: false
|
|
}));
|
|
assert.ok(e);
|
|
|
|
stub.restore();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Zotero.Translate.ItemGetter", function() {
|
|
describe("nextItem", function() {
|
|
it('should return false for an empty database', Zotero.Promise.coroutine(function* () {
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
assert.isFalse(getter.nextItem());
|
|
}));
|
|
it('should return items in order they are supplied', Zotero.Promise.coroutine(function* () {
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
let items, itemIDs, itemURIs;
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
items = [
|
|
yield new Zotero.Item('journalArticle'),
|
|
yield new Zotero.Item('book')
|
|
];
|
|
|
|
itemIDs = [ yield items[0].save(), yield items[1].save() ];
|
|
itemURIs = items.map(i => Zotero.URI.getItemURI(i));
|
|
});
|
|
|
|
getter._itemsLeft = items;
|
|
|
|
assert.equal((getter.nextItem()).uri, itemURIs[0], 'first item comes out first');
|
|
assert.equal((getter.nextItem()).uri, itemURIs[1], 'second item comes out second');
|
|
assert.isFalse((getter.nextItem()), 'end of item queue');
|
|
}));
|
|
it('should return items with tags in expected format', Zotero.Promise.coroutine(function* () {
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
let itemWithAutomaticTag, itemWithManualTag, itemWithMultipleTags
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
itemWithAutomaticTag = new Zotero.Item('journalArticle');
|
|
itemWithAutomaticTag.addTag('automatic tag', 0);
|
|
yield itemWithAutomaticTag.save();
|
|
|
|
itemWithManualTag = new Zotero.Item('journalArticle');
|
|
itemWithManualTag.addTag('manual tag', 1);
|
|
yield itemWithManualTag.save();
|
|
|
|
itemWithMultipleTags = new Zotero.Item('journalArticle');
|
|
itemWithMultipleTags.addTag('tag1', 0);
|
|
itemWithMultipleTags.addTag('tag2', 1);
|
|
yield itemWithMultipleTags.save();
|
|
});
|
|
|
|
let legacyMode = [false, true];
|
|
for (let i=0; i<legacyMode.length; i++) {
|
|
getter._itemsLeft = [itemWithAutomaticTag, itemWithManualTag, itemWithMultipleTags];
|
|
getter.legacy = legacyMode[i];
|
|
let suffix = legacyMode[i] ? ' in legacy mode' : '';
|
|
|
|
// itemWithAutomaticTag
|
|
let translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.tags, 'item contains automatic tags in an array' + suffix);
|
|
assert.isObject(translatorItem.tags[0], 'automatic tag is an object' + suffix);
|
|
assert.equal(translatorItem.tags[0].tag, 'automatic tag', 'automatic tag name provided as "tag" property' + suffix);
|
|
if (legacyMode[i]) {
|
|
assert.equal(translatorItem.tags[0].type, 0, 'automatic tag "type" is 0' + suffix);
|
|
} else {
|
|
assert.isUndefined(translatorItem.tags[0].type, '"type" is undefined for automatic tag' + suffix);
|
|
}
|
|
|
|
// itemWithManualTag
|
|
translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.tags, 'item contains manual tags in an array' + suffix);
|
|
assert.isObject(translatorItem.tags[0], 'manual tag is an object' + suffix);
|
|
assert.equal(translatorItem.tags[0].tag, 'manual tag', 'manual tag name provided as "tag" property' + suffix);
|
|
assert.equal(translatorItem.tags[0].type, 1, 'manual tag "type" is 1' + suffix);
|
|
|
|
// itemWithMultipleTags
|
|
translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.tags, 'item contains multiple tags in an array' + suffix);
|
|
assert.lengthOf(translatorItem.tags, 2, 'expected number of tags returned' + suffix);
|
|
}
|
|
}));
|
|
it('should return item collections in expected format', Zotero.Promise.coroutine(function* () {
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
let items, collections;
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
items = getter._itemsLeft = [
|
|
new Zotero.Item('journalArticle'), // Not in collection
|
|
new Zotero.Item('journalArticle'), // In a single collection
|
|
new Zotero.Item('journalArticle'), //In two collections
|
|
new Zotero.Item('journalArticle') // In a nested collection
|
|
];
|
|
yield Zotero.Promise.all(items.map(item => item.save()));
|
|
|
|
collections = [
|
|
new Zotero.Collection,
|
|
new Zotero.Collection,
|
|
new Zotero.Collection,
|
|
new Zotero.Collection
|
|
];
|
|
collections[0].name = "test1";
|
|
collections[1].name = "test2";
|
|
collections[2].name = "subTest1";
|
|
collections[3].name = "subTest2";
|
|
yield collections[0].save();
|
|
yield collections[1].save();
|
|
collections[2].parentID = collections[0].id;
|
|
collections[3].parentID = collections[1].id;
|
|
yield collections[2].save();
|
|
yield collections[3].save();
|
|
|
|
yield collections[0].addItems([items[1].id, items[2].id]);
|
|
yield collections[1].addItem(items[2].id);
|
|
yield collections[2].addItem(items[3].id);
|
|
});
|
|
|
|
let translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.collections, 'item in library root has a collections array');
|
|
assert.equal(translatorItem.collections.length, 0, 'item in library root does not list any collections');
|
|
|
|
translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.collections, 'item in a single collection has a collections array');
|
|
assert.equal(translatorItem.collections.length, 1, 'item in a single collection lists one collection');
|
|
assert.equal(translatorItem.collections[0], collections[0].key, 'item in a single collection identifies correct collection');
|
|
|
|
translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.collections, 'item in two collections has a collections array');
|
|
assert.equal(translatorItem.collections.length, 2, 'item in two collections lists two collections');
|
|
assert.deepEqual(
|
|
translatorItem.collections.sort(),
|
|
[collections[0].key, collections[1].key].sort(),
|
|
'item in two collections identifies correct collections'
|
|
);
|
|
|
|
translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.collections, 'item in a nested collection has a collections array');
|
|
assert.equal(translatorItem.collections.length, 1, 'item in a single nested collection lists one collection');
|
|
assert.equal(translatorItem.collections[0], collections[2].key, 'item in a single collection identifies correct collection');
|
|
}));
|
|
// it('should return item relations in expected format', Zotero.Promise.coroutine(function* () {
|
|
// let getter = new Zotero.Translate.ItemGetter();
|
|
// let items;
|
|
|
|
// yield Zotero.DB.executeTransaction(function* () {
|
|
// items = [
|
|
// new Zotero.Item('journalArticle'), // Item with no relations
|
|
|
|
// new Zotero.Item('journalArticle'), // Relation set on this item
|
|
// new Zotero.Item('journalArticle'), // To this item
|
|
|
|
// new Zotero.Item('journalArticle'), // This item is related to two items below
|
|
// new Zotero.Item('journalArticle'), // But this item is not related to the item below
|
|
// new Zotero.Item('journalArticle')
|
|
// ];
|
|
// yield Zotero.Promise.all(items.map(item => item.save()));
|
|
|
|
// yield items[1].addRelatedItem(items[2].id);
|
|
|
|
// yield items[3].addRelatedItem(items[4].id);
|
|
// yield items[3].addRelatedItem(items[5].id);
|
|
// });
|
|
|
|
// getter._itemsLeft = items.slice();
|
|
|
|
// let translatorItem = getter.nextItem();
|
|
// assert.isObject(translatorItem.relations, 'item with no relations has a relations object');
|
|
// assert.equal(Object.keys(translatorItem.relations).length, 0, 'item with no relations does not list any relations');
|
|
|
|
// translatorItem = getter.nextItem();
|
|
// assert.isObject(translatorItem.relations, 'item that is the subject of a single relation has a relations object');
|
|
// assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the subject of a single relation list one relations predicate');
|
|
// assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the subject of a single relation uses "dc:relation" as the predicate');
|
|
// assert.isString(translatorItem.relations['dc:relation'], 'item that is the subject of a single relation lists "dc:relation" object as a string');
|
|
// assert.equal(translatorItem.relations['dc:relation'], Zotero.URI.getItemURI(items[2]), 'item that is the subject of a single relation identifies correct object URI');
|
|
|
|
// translatorItem = getter.nextItem();
|
|
// assert.isObject(translatorItem.relations, 'item that is the object of a single relation has a relations object');
|
|
// assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the object of a single relation list one relations predicate');
|
|
// assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the object of a single relation uses "dc:relation" as the predicate');
|
|
// assert.isString(translatorItem.relations['dc:relation'], 'item that is the object of a single relation lists "dc:relation" object as a string');
|
|
// assert.equal(translatorItem.relations['dc:relation'], Zotero.URI.getItemURI(items[1]), 'item that is the object of a single relation identifies correct subject URI');
|
|
|
|
// translatorItem = getter.nextItem();
|
|
// assert.isObject(translatorItem.relations, 'item that is the subject of two relations has a relations object');
|
|
// assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the subject of two relations list one relations predicate');
|
|
// assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the subject of two relations uses "dc:relation" as the predicate');
|
|
// assert.isArray(translatorItem.relations['dc:relation'], 'item that is the subject of two relations lists "dc:relation" object as an array');
|
|
// assert.equal(translatorItem.relations['dc:relation'].length, 2, 'item that is the subject of two relations lists two relations in the "dc:relation" array');
|
|
// assert.deepEqual(translatorItem.relations['dc:relation'].sort(),
|
|
// [Zotero.URI.getItemURI(items[4]), Zotero.URI.getItemURI(items[5])].sort(),
|
|
// 'item that is the subject of two relations identifies correct object URIs'
|
|
// );
|
|
|
|
// translatorItem = getter.nextItem();
|
|
// assert.isObject(translatorItem.relations, 'item that is the object of one relation from item with two relations has a relations object');
|
|
// assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the object of one relation from item with two relations list one relations predicate');
|
|
// assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the object of one relation from item with two relations uses "dc:relation" as the predicate');
|
|
// assert.isString(translatorItem.relations['dc:relation'], 'item that is the object of one relation from item with two relations lists "dc:relation" object as a string');
|
|
// assert.equal(translatorItem.relations['dc:relation'], Zotero.URI.getItemURI(items[3]), 'item that is the object of one relation from item with two relations identifies correct subject URI');
|
|
// }));
|
|
it('should return standalone note in expected format', Zotero.Promise.coroutine(function* () {
|
|
let relatedItem, note, collection;
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
relatedItem = new Zotero.Item('journalArticle');
|
|
yield relatedItem.save();
|
|
|
|
note = new Zotero.Item('note');
|
|
note.setNote('Note');
|
|
note.addTag('automaticTag', 0);
|
|
note.addTag('manualTag', 1);
|
|
// note.addRelatedItem(relatedItem.id);
|
|
yield note.save();
|
|
|
|
collection = new Zotero.Collection;
|
|
collection.name = 'test';
|
|
yield collection.save();
|
|
yield collection.addItem(note.id);
|
|
});
|
|
|
|
let legacyMode = [false, true];
|
|
for (let i=0; i<legacyMode.length; i++) {
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
getter._itemsLeft = [note];
|
|
let legacy = getter.legacy = legacyMode[i];
|
|
let suffix = legacy ? ' in legacy mode' : '';
|
|
|
|
let translatorNote = getter.nextItem();
|
|
assert.isDefined(translatorNote, 'returns standalone note' + suffix);
|
|
assert.equal(translatorNote.itemType, 'note', 'itemType is correct' + suffix);
|
|
assert.equal(translatorNote.note, 'Note', 'note is correct' + suffix);
|
|
|
|
assert.isString(translatorNote.dateAdded, 'dateAdded is string' + suffix);
|
|
assert.isString(translatorNote.dateModified, 'dateModified is string' + suffix);
|
|
|
|
if (legacy) {
|
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
|
|
|
assert.isNumber(translatorNote.itemID, 'itemID is set' + suffix);
|
|
assert.isString(translatorNote.key, 'key is set' + suffix);
|
|
} else {
|
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
|
}
|
|
|
|
// Tags
|
|
assert.isArray(translatorNote.tags, 'contains tags as array' + suffix);
|
|
assert.equal(translatorNote.tags.length, 2, 'contains correct number of tags' + suffix);
|
|
let possibleTags = [
|
|
{ tag: 'automaticTag', type: 0 },
|
|
{ tag: 'manualTag', type: 1 }
|
|
];
|
|
for (let i=0; i<possibleTags.length; i++) {
|
|
let match = false;
|
|
for (let j=0; j<translatorNote.tags.length; j++) {
|
|
if (possibleTags[i].tag == translatorNote.tags[j].tag) {
|
|
let type = possibleTags[i].type;
|
|
if (!legacy && type == 0) type = undefined;
|
|
|
|
assert.equal(translatorNote.tags[j].type, type, possibleTags[i].tag + ' tag is correct' + suffix);
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
assert.isTrue(match, 'has ' + possibleTags[i].tag + ' tag ' + suffix);
|
|
}
|
|
|
|
// Relations
|
|
// assert.isObject(translatorNote.relations, 'has relations as object' + suffix);
|
|
// assert.equal(translatorNote.relations['dc:relation'], Zotero.URI.getItemURI(relatedItem), 'relation is correct' + suffix);
|
|
/** TODO: test other relations and multiple relations per predicate (should be an array) **/
|
|
|
|
if (!legacy) {
|
|
// Collections
|
|
assert.isArray(translatorNote.collections, 'has a collections array' + suffix);
|
|
assert.equal(translatorNote.collections.length, 1, 'lists one collection' + suffix);
|
|
assert.equal(translatorNote.collections[0], collection.key, 'identifies correct collection' + suffix);
|
|
}
|
|
}
|
|
}));
|
|
it('should return attached note in expected format', Zotero.Promise.coroutine(function* () {
|
|
let relatedItem, items, collection, note;
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
relatedItem = new Zotero.Item('journalArticle');
|
|
yield relatedItem.save();
|
|
|
|
items = [
|
|
new Zotero.Item('journalArticle'),
|
|
new Zotero.Item('journalArticle')
|
|
];
|
|
yield Zotero.Promise.all(items.map(item => item.save()));
|
|
|
|
collection = new Zotero.Collection;
|
|
collection.name = 'test';
|
|
yield collection.save();
|
|
yield collection.addItem(items[0].id);
|
|
yield collection.addItem(items[1].id);
|
|
|
|
note = new Zotero.Item('note');
|
|
note.setNote('Note');
|
|
note.addTag('automaticTag', 0);
|
|
note.addTag('manualTag', 1);
|
|
yield note.save();
|
|
|
|
// note.addRelatedItem(relatedItem.id);
|
|
});
|
|
|
|
let legacyMode = [false, true];
|
|
for (let i=0; i<legacyMode.length; i++) {
|
|
let item = items[i];
|
|
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
getter._itemsLeft = [item];
|
|
let legacy = getter.legacy = legacyMode[i];
|
|
let suffix = legacy ? ' in legacy mode' : '';
|
|
|
|
let translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.notes, 'item with no notes contains notes array' + suffix);
|
|
assert.equal(translatorItem.notes.length, 0, 'item with no notes contains empty notes array' + suffix);
|
|
|
|
note.parentID = item.id;
|
|
yield note.saveTx();
|
|
|
|
getter = new Zotero.Translate.ItemGetter();
|
|
getter._itemsLeft = [item];
|
|
getter.legacy = legacy;
|
|
|
|
translatorItem = getter.nextItem();
|
|
assert.isArray(translatorItem.notes, 'item with no notes contains notes array' + suffix);
|
|
assert.equal(translatorItem.notes.length, 1, 'item with one note contains array with one note' + suffix);
|
|
|
|
let translatorNote = translatorItem.notes[0];
|
|
assert.equal(translatorNote.itemType, 'note', 'itemType is correct' + suffix);
|
|
assert.equal(translatorNote.note, 'Note', 'note is correct' + suffix);
|
|
|
|
assert.isString(translatorNote.dateAdded, 'dateAdded is string' + suffix);
|
|
assert.isString(translatorNote.dateModified, 'dateModified is string' + suffix);
|
|
|
|
if (legacy) {
|
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
|
|
|
assert.isNumber(translatorNote.itemID, 'itemID is set' + suffix);
|
|
assert.isString(translatorNote.key, 'key is set' + suffix);
|
|
} else {
|
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
|
}
|
|
|
|
// Tags
|
|
assert.isArray(translatorNote.tags, 'contains tags as array' + suffix);
|
|
assert.equal(translatorNote.tags.length, 2, 'contains correct number of tags' + suffix);
|
|
let possibleTags = [
|
|
{ tag: 'automaticTag', type: 0 },
|
|
{ tag: 'manualTag', type: 1 }
|
|
];
|
|
for (let i=0; i<possibleTags.length; i++) {
|
|
let match = false;
|
|
for (let j=0; j<translatorNote.tags.length; j++) {
|
|
if (possibleTags[i].tag == translatorNote.tags[j].tag) {
|
|
let type = possibleTags[i].type;
|
|
if (!legacy && type == 0) type = undefined;
|
|
|
|
assert.equal(translatorNote.tags[j].type, type, possibleTags[i].tag + ' tag is correct' + suffix);
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
assert.isTrue(match, 'has ' + possibleTags[i].tag + ' tag ' + suffix);
|
|
}
|
|
|
|
// Relations
|
|
// assert.isObject(translatorNote.relations, 'has relations as object' + suffix);
|
|
// assert.equal(translatorNote.relations['dc:relation'], Zotero.URI.getItemURI(relatedItem), 'relation is correct' + suffix);
|
|
/** TODO: test other relations and multiple relations per predicate (should be an array) **/
|
|
|
|
if (!legacy) {
|
|
// Collections
|
|
assert.isUndefined(translatorNote.collections, 'has no collections array' + suffix);
|
|
}
|
|
}
|
|
}));
|
|
|
|
it('should return stored/linked file and URI attachments in expected format', Zotero.Promise.coroutine(function* () {
|
|
this.timeout(60000);
|
|
let file = getTestPDF();
|
|
let item, relatedItem;
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
item = new Zotero.Item('journalArticle');
|
|
yield item.save();
|
|
relatedItem = new Zotero.Item('journalArticle');
|
|
yield relatedItem.save();
|
|
});
|
|
|
|
// Attachment items
|
|
let attachments = [
|
|
yield Zotero.Attachments.importFromFile({"file":file}), // Standalone stored file
|
|
yield Zotero.Attachments.linkFromFile({"file":file}), // Standalone link to file
|
|
yield Zotero.Attachments.importFromFile({"file":file, "parentItemID":item.id}), // Attached stored file
|
|
yield Zotero.Attachments.linkFromFile({"file":file, "parentItemID":item.id}), // Attached link to file
|
|
yield Zotero.Attachments.linkFromURL({"url":'http://example.com', "parentItemID":item.id, "contentType":'application/pdf', "title":'empty.pdf'}) // Attached link to URL
|
|
];
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
// Make sure all fields are populated
|
|
for (let i=0; i<attachments.length; i++) {
|
|
let attachment = attachments[i];
|
|
attachment.setField('accessDate', '2001-02-03 12:13:14');
|
|
attachment.attachmentCharset = 'utf-8';
|
|
attachment.setField('url', 'http://example.com');
|
|
attachment.setNote('note');
|
|
|
|
attachment.addTag('automaticTag', 0);
|
|
attachment.addTag('manualTag', 1);
|
|
|
|
// attachment.addRelatedItem(relatedItem.id);
|
|
|
|
yield attachment.save();
|
|
}
|
|
});
|
|
|
|
let items = [ attachments[0], attachments[1], item ]; // Standalone attachments and item with child attachments
|
|
|
|
// Run tests
|
|
let legacyMode = [false, true];
|
|
for (let i=0; i<legacyMode.length; i++) {
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
getter._itemsLeft = items.slice();
|
|
|
|
let exportDir = yield getTempDirectory();
|
|
getter._exportFileDirectory = Components.classes["@mozilla.org/file/local;1"]
|
|
.createInstance(Components.interfaces.nsILocalFile);
|
|
getter._exportFileDirectory.initWithPath(exportDir);
|
|
|
|
let legacy = getter.legacy = legacyMode[i];
|
|
let suffix = legacy ? ' in legacy mode' : '';
|
|
|
|
// Gather all standalone and child attachments into a single array,
|
|
// since tests are mostly the same
|
|
let translatorAttachments = [], translatorItem;
|
|
let itemsLeft = items.length, attachmentsLeft = attachments.length;
|
|
while (translatorItem = getter.nextItem()) {
|
|
assert.isString(translatorItem.itemType, 'itemType is set' + suffix);
|
|
|
|
// Standalone attachments
|
|
if (translatorItem.itemType == 'attachment') {
|
|
translatorAttachments.push({
|
|
child: false,
|
|
attachment: translatorItem
|
|
});
|
|
attachmentsLeft--;
|
|
|
|
// Child attachments
|
|
} else if (translatorItem.itemType == 'journalArticle') {
|
|
assert.isArray(translatorItem.attachments, 'item contains attachment array' + suffix);
|
|
assert.equal(translatorItem.attachments.length, 3, 'attachment array contains all items' + suffix);
|
|
|
|
for (let i=0; i<translatorItem.attachments.length; i++) {
|
|
let attachment = translatorItem.attachments[i];
|
|
assert.equal(attachment.itemType, 'attachment', 'item attachment is of itemType "attachment"' + suffix);
|
|
|
|
translatorAttachments.push({
|
|
child: true,
|
|
attachment: attachment
|
|
});
|
|
|
|
attachmentsLeft--;
|
|
}
|
|
|
|
// Unexpected
|
|
} else {
|
|
assert.fail(translatorItem.itemType, 'attachment or journalArticle', 'expected itemType returned');
|
|
}
|
|
|
|
itemsLeft--;
|
|
}
|
|
|
|
assert.equal(itemsLeft, 0, 'all items returned by getter');
|
|
assert.equal(attachmentsLeft, 0, 'all attachments returned by getter');
|
|
|
|
// Since we make no guarantees on the order of child attachments,
|
|
// we have to rely on URI as the identifier
|
|
let uriMap = {};
|
|
for (let i=0; i<attachments.length; i++) {
|
|
uriMap[Zotero.URI.getItemURI(attachments[i])] = attachments[i];
|
|
}
|
|
|
|
for (let j=0; j<translatorAttachments.length; j++) {
|
|
let childAttachment = translatorAttachments[j].child;
|
|
let attachment = translatorAttachments[j].attachment;
|
|
assert.isString(attachment.uri, 'uri is set' + suffix);
|
|
|
|
let zoteroItem = uriMap[attachment.uri];
|
|
assert.isDefined(zoteroItem, 'uri is correct' + suffix);
|
|
delete uriMap[attachment.uri];
|
|
|
|
let storedFile = zoteroItem.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE
|
|
|| zoteroItem.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL;
|
|
let linkToURL = zoteroItem.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL;
|
|
|
|
let prefix = (childAttachment ? 'attached ' : '')
|
|
+ (storedFile ? 'stored ' : 'link to ')
|
|
+ (linkToURL ? 'URL ' : 'file ');
|
|
|
|
// Set fields
|
|
assert.equal(attachment.itemType, 'attachment', prefix + 'itemType is correct' + suffix);
|
|
assert.equal(attachment.title, 'empty.pdf', prefix + 'title is correct' + suffix);
|
|
assert.equal(attachment.url, 'http://example.com', prefix + 'url is correct' + suffix);
|
|
assert.equal(attachment.note, 'note', prefix + 'note is correct' + suffix);
|
|
|
|
// Automatically set fields
|
|
assert.isString(attachment.dateAdded, prefix + 'dateAdded is set' + suffix);
|
|
assert.isString(attachment.dateModified, prefix + 'dateModified is set' + suffix);
|
|
|
|
// Legacy mode fields
|
|
if (legacy) {
|
|
assert.isNumber(attachment.itemID, prefix + 'itemID is set' + suffix);
|
|
assert.isString(attachment.key, prefix + 'key is set' + suffix);
|
|
assert.equal(attachment.mimeType, 'application/pdf', prefix + 'mimeType is correct' + suffix);
|
|
|
|
assert.equal(attachment.accessDate, '2001-02-03 12:13:14', prefix + 'accessDate is correct' + suffix);
|
|
|
|
assert.isTrue(sqlDateTimeRe.test(attachment.dateAdded), prefix + 'dateAdded matches SQL format' + suffix);
|
|
assert.isTrue(sqlDateTimeRe.test(attachment.dateModified), prefix + 'dateModified matches SQL format' + suffix);
|
|
} else {
|
|
assert.equal(attachment.contentType, 'application/pdf', prefix + 'contentType is correct' + suffix);
|
|
|
|
assert.equal(attachment.accessDate, '2001-02-03T12:13:14Z', prefix + 'accessDate is correct' + suffix);
|
|
|
|
assert.isTrue(isoDateTimeRe.test(attachment.dateAdded), prefix + 'dateAdded matches ISO-8601 format' + suffix);
|
|
assert.isTrue(isoDateTimeRe.test(attachment.dateModified), prefix + 'dateModified matches ISO-8601 format' + suffix);
|
|
}
|
|
|
|
if (!linkToURL) {
|
|
// localPath
|
|
assert.isString(attachment.localPath, prefix + 'localPath is set' + suffix);
|
|
let attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
|
.createInstance(Components.interfaces.nsILocalFile);
|
|
attachmentFile.initWithPath(attachment.localPath);
|
|
assert.isTrue(attachmentFile.exists(), prefix + 'localPath points to a file' + suffix);
|
|
assert.isTrue(attachmentFile.equals(attachments[j].getFile()), prefix + 'localPath points to the correct file' + suffix);
|
|
|
|
assert.equal(attachment.filename, 'empty.pdf', prefix + 'filename is correct' + suffix);
|
|
assert.equal(attachment.defaultPath, 'files/' + attachments[j].id + '/' + attachment.filename, prefix + 'defaultPath is correct' + suffix);
|
|
|
|
// saveFile function
|
|
assert.isFunction(attachment.saveFile, prefix + 'has saveFile function' + suffix);
|
|
attachment.saveFile(attachment.defaultPath);
|
|
assert.equal(attachment.path, OS.Path.join(exportDir, OS.Path.normalize(attachment.defaultPath)), prefix + 'path is set correctly after saveFile call' + suffix);
|
|
|
|
let fileExists = yield OS.File.exists(attachment.path);
|
|
assert.isTrue(fileExists, prefix + 'file was copied to the correct path by saveFile function' + suffix);
|
|
fileExists = yield OS.File.exists(attachment.localPath);
|
|
assert.isTrue(fileExists, prefix + 'file was not removed from original location' + suffix);
|
|
|
|
assert.throws(attachment.saveFile.bind(attachment, attachment.defaultPath), /^ERROR_FILE_EXISTS /, prefix + 'saveFile does not overwrite existing file by default' + suffix);
|
|
assert.throws(attachment.saveFile.bind(attachment, 'file/../../'), /./, prefix + 'saveFile does not allow exporting outside export directory' + suffix);
|
|
/** TODO: check if overwriting existing file works **/
|
|
}
|
|
|
|
// Tags
|
|
assert.isArray(attachment.tags, prefix + 'contains tags as array' + suffix);
|
|
assert.equal(attachment.tags.length, 2, prefix + 'contains correct number of tags' + suffix);
|
|
let possibleTags = [
|
|
{ tag: 'automaticTag', type: 0 },
|
|
{ tag: 'manualTag', type: 1 }
|
|
];
|
|
for (let i=0; i<possibleTags.length; i++) {
|
|
let match = false;
|
|
for (let j=0; j<attachment.tags.length; j++) {
|
|
if (possibleTags[i].tag == attachment.tags[j].tag) {
|
|
let type = possibleTags[i].type;
|
|
if (!legacy && type == 0) type = undefined;
|
|
|
|
assert.equal(attachment.tags[j].type, type, prefix + possibleTags[i].tag + ' tag is correct' + suffix);
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
assert.isTrue(match, prefix + ' has ' + possibleTags[i].tag + ' tag ' + suffix);
|
|
}
|
|
|
|
// Relations
|
|
// assert.isObject(attachment.relations, prefix + 'has relations as object' + suffix);
|
|
// assert.equal(attachment.relations['dc:relation'], Zotero.URI.getItemURI(relatedItem), prefix + 'relation is correct' + suffix);
|
|
/** TODO: test other relations and multiple relations per predicate (should be an array) **/
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
|
|
describe("#setCollection()", function () {
|
|
it("should add collection items", function* () {
|
|
var col = yield createDataObject('collection');
|
|
var item1 = yield createDataObject('item', { collections: [col.id] });
|
|
var item2 = yield createDataObject('item', { collections: [col.id] });
|
|
var item3 = yield createDataObject('item');
|
|
|
|
let getter = new Zotero.Translate.ItemGetter();
|
|
getter.setCollection(col);
|
|
|
|
assert.equal(getter.numItems, 2);
|
|
});
|
|
});
|
|
|
|
describe("#_attachmentToArray()", function () {
|
|
it("should handle missing attachment files", function* () {
|
|
var item = yield importFileAttachment('test.png');
|
|
var path = item.getFilePath();
|
|
// Delete attachment file
|
|
yield OS.File.remove(path);
|
|
|
|
var translation = new Zotero.Translate.Export();
|
|
var tmpDir = yield getTempDirectory();
|
|
var exportDir = OS.Path.join(tmpDir, 'export');
|
|
translation.setLocation(Zotero.File.pathToFile(exportDir));
|
|
translation.setItems([item]);
|
|
translation.setTranslator('14763d24-8ba0-45df-8f52-b8d1108e7ac9'); // Zotero RDF
|
|
translation.setDisplayOptions({
|
|
exportFileData: true
|
|
});
|
|
yield translation.translate();
|
|
|
|
var exportFile = OS.Path.join(exportDir, 'export.rdf');
|
|
assert.isAbove((yield OS.File.stat(exportFile)).size, 0);
|
|
});
|
|
});
|
|
});
|
|
} |