new function() { Components.utils.import("resource://gre/modules/osfile.jsm"); Components.utils.import("resource://zotero-unit/httpd.js"); const { HiddenBrowser } = ChromeUtils.import('chrome://zotero/content/HiddenBrowser.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, translateOptions = {}) { 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](); if (translatorType == "web") { let doc = new DOMParser().parseFromString('', 'text/html'); doc = Zotero.HTTP.wrapDocument(doc, 'https://www.example.com/'); translate.setDocument(doc); } 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; iTitleBody"); }; httpd.registerPathHandler("/1", { handle: handler }); httpd.registerPathHandler("/2", { handle: handler }); var items = [{ itemType: "book", title: "Item", attachments: [ // With mimeType { itemType: "attachment", linkMode: Zotero.Attachments.LINK_MODE_LINKED_URL, title: "Link 1", url: baseURL + "1", mimeType: 'text/html' }, // Without mimeType { itemType: "attachment", linkMode: Zotero.Attachments.LINK_MODE_LINKED_URL, title: "Link 2", url: baseURL + "2" } ] }]; var newItems = itemsArrayToObject(await saveItemsThroughTranslator("import", items)); assert.equal(callCount, 0); var attachments = await Zotero.Items.getAsync(newItems.Item.getAttachments()); assert.equal(attachments.length, 2); assert.equal(attachments[0].getField("title"), "Link 1"); assert.equal(attachments[0].getField("url"), baseURL + "1"); assert.equal(attachments[0].attachmentContentType, "text/html"); assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL); assert.equal(attachments[1].getField("title"), "Link 2"); assert.equal(attachments[1].getField("url"), baseURL + "2"); assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL); assert.equal(attachments[1].attachmentContentType, ''); await new Promise(function (resolve) { httpd.stop(resolve); }); }); it("import translators should save linked-URL attachments with savingAttachments: false", async function () { var json = [ { itemType: "journalArticle", title: "Parent Item", attachments: [ // snapshot: false { title: "Link", mimeType: "text/html", url: "http://example.com", snapshot: false }, // linkMode (used by RDF import) { title: "Link", mimeType: "text/html", url: "http://example.com", linkMode: Zotero.Attachments.LINK_MODE_LINKED_URL } ] } ]; var newItems = itemsArrayToObject( await saveItemsThroughTranslator( "import", json, { saveAttachments: false } ) ); var attachmentIDs = newItems["Parent Item"].getAttachments(); assert.lengthOf(attachmentIDs, 2); var attachments = await Zotero.Items.getAsync(attachmentIDs); assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL); assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL); }); it("import translators should save linked-file attachments with linkFiles: true", async function () { var testDir = getTestDataDirectory().path; var file1 = OS.Path.join(testDir, 'test.pdf'); var file2 = OS.Path.join(testDir, 'test.html'); var file2URL = "http://example.com"; var json = [ { itemType: "journalArticle", title: "Parent Item", attachments: [ { title: "PDF", mimeType: "application/pdf", path: file1 }, { title: "Snapshot", mimeType: "text/html", charset: "utf-8", url: file2URL, path: file2 } ] } ]; var newItems = itemsArrayToObject( await saveItemsThroughTranslator( "import", json, { linkFiles: true } ) ); var attachmentIDs = newItems["Parent Item"].getAttachments(); assert.lengthOf(attachmentIDs, 2); var attachments = await Zotero.Items.getAsync(attachmentIDs); assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE); assert.equal(attachments[0].attachmentContentType, 'application/pdf'); assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE); assert.equal(attachments[1].attachmentContentType, 'text/html'); assert.equal(attachments[1].attachmentCharset, 'utf-8'); assert.equal(attachments[1].note, file2URL); }); it("import translators shouldn't save linked-file attachment with linkFiles: true if path is within current storage directory", async function () { var attachment = await importFileAttachment('test.png'); var path = attachment.getFilePath(); var json = [ { itemType: "journalArticle", title: "Parent Item", attachments: [ { title: "PDF", mimeType: "application/pdf", path } ] } ]; var newItems = itemsArrayToObject( await saveItemsThroughTranslator( "import", json, { linkFiles: true } ) ); var attachmentIDs = newItems["Parent Item"].getAttachments(); assert.lengthOf(attachmentIDs, 1); var attachments = await Zotero.Items.getAsync(attachmentIDs); assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_FILE); var newPath = attachments[0].getFilePath(); assert.ok(newPath); assert.notEqual(newPath, path); }); 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 set accessDate to current date for CURRENT_TIMESTAMP', function* () { let myItem = { itemType: "webpage", title: "Test Item", url: "https://www.zotero.org/", accessDate: 'CURRENT_TIMESTAMP' }; 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.note, "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.note, "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.note, "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 browser document', function* () { let browser = yield HiddenBrowser.create("http://127.0.0.1:23119/test/translate/test.html"); let doc = yield HiddenBrowser.getDocument(browser); 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 = Zotero.Items.get(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.note, "attachment note"); assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL); assert.equal(snapshot.attachmentContentType, "text/html"); checkTestTags(snapshot, true); HiddenBrowser.destroy(browser); }); it('web translators should save attachment from non-browser document', function* () { return Zotero.HTTP.processDocuments( "http://127.0.0.1:23119/test/translate/test.html", async function (doc) { 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 = await translate.translate(); assert.equal(newItems.length, 1); let containedAttachments = Zotero.Items.get(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.note, "attachment note"); assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL); assert.equal(snapshot.attachmentContentType, "text/html"); checkTestTags(snapshot, true); } ); }); 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.note, "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("#processDocuments()", function () { var url = "http://127.0.0.1:23119/test/translate/test.html"; var doc; beforeEach(function* () { // This is the main processDocuments, not the translation sandbox one being tested doc = (yield Zotero.HTTP.processDocuments(url, doc => doc))[0]; }); it("should provide document object", async function () { var translate = new Zotero.Translate.Web(); translate.setDocument(doc); translate.setTranslator( buildDummyTranslator( 4, `function detectWeb() {} function doWeb(doc) { ZU.processDocuments( doc.location.href + '?t', function (doc) { var item = new Zotero.Item("book"); item.title = "Container Item"; // document.location item.url = doc.location.href; // document.evaluate() item.extra = doc .evaluate('//p', doc, null, XPathResult.ANY_TYPE, null) .iterateNext() .textContent; item.attachments = [{ document: doc, title: "Snapshot from Document", note: "attachment note", tags: ${JSON.stringify(TEST_TAGS)} }]; item.complete(); } ); }` ) ); var newItems = await translate.translate(); assert.equal(newItems.length, 1); var item = newItems[0]; assert.equal(item.getField('url'), url + '?t'); assert.include(item.getField('extra'), 'your research sources'); var containedAttachments = Zotero.Items.get(newItems[0].getAttachments()); assert.equal(containedAttachments.length, 1); var snapshot = containedAttachments[0]; assert.equal(snapshot.getField("url"), url + '?t'); assert.equal(snapshot.note, "attachment note"); assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL); assert.equal(snapshot.attachmentContentType, "text/html"); checkTestTags(snapshot, true); }); it("should use loaded document instead of reloading if possible", function* () { var translate = new Zotero.Translate.Web(); translate.setDocument(doc); translate.setTranslator( buildDummyTranslator( 4, `function detectWeb() {} function doWeb(doc) { ZU.processDocuments( doc.location.href, function (doc) { var item = new Zotero.Item("book"); item.title = "Container Item"; // document.location item.url = doc.location.href; // document.evaluate() item.extra = doc .evaluate('//p', doc, null, XPathResult.ANY_TYPE, null) .iterateNext() .textContent; item.attachments = [{ document: doc, title: "Snapshot from Document", note: "attachment note", tags: ${JSON.stringify(TEST_TAGS)} }]; item.complete(); } ); }` ) ); var newItems = yield translate.translate(); assert.equal(newItems.length, 1); var item = newItems[0]; assert.equal(item.getField('url'), url); assert.include(item.getField('extra'), 'your research sources'); var containedAttachments = Zotero.Items.get(newItems[0].getAttachments()); assert.equal(containedAttachments.length, 1); var snapshot = containedAttachments[0]; assert.equal(snapshot.getField("url"), url); assert.equal(snapshot.note, "attachment note"); assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL); assert.equal(snapshot.attachmentContentType, "text/html"); checkTestTags(snapshot, true); }); }); describe("#setTranslatorProvider()", function () { var url = "http://127.0.0.1:23119/test/translate/test.html"; var doc; beforeEach(function* () { // This is the main processDocuments, not the translation sandbox one being tested doc = (yield Zotero.HTTP.processDocuments(url, doc => doc))[0]; }); it("should set a custom version of Zotero.Translators", async function () { // Create a dummy translator to be returned by the stub methods var info = { translatorID: "e6111720-1f6c-42b0-a487-99b9fa50b8a1", label: "Test", creator: "Creator", target: "^http:\/\/127.0.0.1:23119\/test", minVersion: "5.0", maxVersion: "", priority: 100, translatorType: 4, browserSupport: "gcsibv", lastUpdated: "2019-07-10 05:50:39", cacheCode: true }; info.code = JSON.stringify(info, null, '\t') + "\n\n" + "function detectWeb(doc, url) {" + "return 'journalArticle';" + "}\n" + "function doWeb(doc, url) {" + "var item = new Zotero.Item('journalArticle');" + "item.title = 'Test';" + "item.complete();" + "}\n"; var translator = new Zotero.Translator(info); var translate = new Zotero.Translate.Web(); var provider = Zotero.Translators.makeTranslatorProvider({ get: function (translatorID) { if (translatorID == info.translatorID) { return translator; } return false; }, getAllForType: async function (type) { var translators = []; if (type == 'web') { translators.push(translator); } return translators; } }); translate.setTranslatorProvider(provider); translate.setDocument(doc); var translators = await translate.getTranslators(); translate.setTranslator(translators[0]); var newItems = await translate.translate(); assert.equal(newItems.length, 1); var item = newItems[0]; assert.equal(item.getField('title'), 'Test'); }); it("should set a custom version of Zotero.Translators in a child translator", async function () { // Create dummy translators to be returned by the stub methods var info1 = { translatorID: "e6111720-1f6c-42b0-a487-99b9fa50b8a1", label: "Test", creator: "Creator", target: "^http:\/\/127.0.0.1:23119\/test", minVersion: "5.0", maxVersion: "", priority: 100, translatorType: 4, browserSupport: "gcsibv", lastUpdated: "2019-07-10 05:50:39", cacheCode: true }; info1.code = JSON.stringify(info1, null, '\t') + "\n\n" + "function detectWeb(doc, url) {" + "return 'journalArticle';" + "}\n" + "function doWeb(doc, url) {" + "var translator = Zotero.loadTranslator('import');" + "translator.setTranslator('86e58f50-4e2d-4ee8-8a20-bafa225381fa');" + "translator.setString('foo\\n');" + "translator.setHandler('itemDone', function(obj, item) {" + "item.complete();" + "});" + "translator.translate();" + "}\n"; var translator1 = new Zotero.Translator(info1); var info2 = { translatorID: "86e58f50-4e2d-4ee8-8a20-bafa225381fa", label: "Child Test", creator: "Creator", target: "", minVersion: "5.0", maxVersion: "", priority: 100, translatorType: 3, browserSupport: "gcsibv", lastUpdated: "2019-07-19 06:22:21", cacheCode: true }; info2.code = JSON.stringify(info2, null, '\t') + "\n\n" + "function detectImport() {" + "return true;" + "}\n" + "function doImport() {" + "var item = new Zotero.Item('journalArticle');" + "item.title = 'Test';" + "item.complete();" + "}\n"; var translator2 = new Zotero.Translator(info2); var translate = new Zotero.Translate.Web(); var provider = Zotero.Translators.makeTranslatorProvider({ get: function (translatorID) { switch (translatorID) { case info1.translatorID: return translator1; case info2.translatorID: return translator2; } return false; }, getAllForType: async function (type) { var translators = []; if (type == 'web') { translators.push(translator1); } if (type == 'import') { translators.push(translator2); } return translators; } }); translate.setTranslatorProvider(provider); translate.setDocument(doc); var translators = await translate.getTranslators(); translate.setTranslator(translators[0]); var newItems = await translate.translate(); assert.equal(newItems.length, 1); var item = newItems[0]; assert.equal(item.getField('title'), 'Test'); }); }); 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()); }); it("should round-trip collections via Zotero RDF", async function () { this.timeout(60000); await resetDB(); var c1 = await createDataObject('collection', { name: '1' }); var c2 = await createDataObject('collection', { name: '2', parentID: c1.id }); var c3 = await createDataObject('collection', { name: '3', parentID: c2.id }); var c4 = await createDataObject('collection', { name: '4', parentID: c3.id }); var c5 = await createDataObject('collection', { name: '5', parentID: c4.id }); // Add item, standalone note, and standalone attachment to collection var item = await createDataObject( 'item', { collections: [c5.id], title: Zotero.Utilities.randomString() } ); var note = await createDataObject( 'item', { itemType: 'note', collections: [c5.id], note: Zotero.Utilities.randomString() } ); var attachment = await importFileAttachment('test.pdf', { url: 'https://example.com/test.pdf', title: Zotero.Utilities.randomString(), collections: [c5.id] }); var tmpDir = await getTempDirectory(); var libraryExportDir = OS.Path.join(tmpDir, 'export-library'); var libraryExportFile = OS.Path.join(libraryExportDir, 'export-library.rdf'); var collectionExportDir = OS.Path.join(tmpDir, 'export-collection'); var collectionExportFile = OS.Path.join(collectionExportDir, 'export-collection.rdf'); // Export library var translation = new Zotero.Translate.Export(); translation.setLocation(Zotero.File.pathToFile(libraryExportDir)); translation.setLibraryID(Zotero.Libraries.userLibraryID); translation.setDisplayOptions({ exportFileData: true, exportNotes: true }); translation.setTranslator('14763d24-8ba0-45df-8f52-b8d1108e7ac9'); // Zotero RDF await translation.translate(); // Export top-most collection translation = new Zotero.Translate.Export(); translation.setLocation(Zotero.File.pathToFile(collectionExportDir)); translation.setCollection(c1); translation.setDisplayOptions({ exportFileData: true, exportNotes: true }); translation.setTranslator('14763d24-8ba0-45df-8f52-b8d1108e7ac9'); // Zotero RDF await translation.translate(); async function check(file, mode) { var collectionNames = [c1.name, c2.name, c3.name, c4.name, c5.name]; var translation = new Zotero.Translate.Import(); translation.setLocation(Zotero.File.pathToFile(file)); var translators = await translation.getTranslators(); translation.setTranslator(translators[0]); var importCollection = await createDataObject('collection'); await translation.translate({ libraryID: Zotero.Libraries.userLibraryID, collections: [importCollection.id] }); // When exporting a library, the top-most collection should be included. When // exporting a collection, the selected collection isn't included, so remove it. if (mode == 'collection') { collectionNames.shift(); } var collections = importCollection.getChildCollections(); assert.lengthOf(collections, 1, mode); assert.equal(collections[0].name, collectionNames.shift(), mode) var name; while (name = collectionNames.shift()) { collections = collections[0].getChildCollections(); assert.lengthOf(collections, 1, mode); let c = collections[0]; assert.equal(c.name, name, mode); // Get the collection we imported items into if (name == c5.name) { // Make sure items were imported and added to collection let titles = c.getChildItems().map(x => x.getDisplayTitle()); assert.sameMembers(titles, [item, note, attachment].map(x => x.getDisplayTitle())); } // Other collections should be empty else { assert.lengthOf(c.getChildItems(), 0); } } } await check(libraryExportFile, 'library'); await check(collectionExportFile, 'collection'); }); }); describe("Async translators", function () { var htmlURL = "http://127.0.0.1:23119/test/translate/test.html"; var jsonURL = "http://127.0.0.1:23119/test/translate/test.json"; var notFoundURL = "http://127.0.0.1:23119/test/translate/does_not_exist.html" var doc; before(function* () { setupAttachmentEndpoints(); setupAsyncEndpoints(); doc = (yield Zotero.HTTP.processDocuments(htmlURL, doc => doc))[0]; }); it('should support async detectWeb', async function () { var info = { translatorID: "e6111720-1f6c-42b0-a487-99b9fa50b8a1", label: "Test", creator: "Creator", target: "^http:\/\/127.0.0.1:23119\/test", minVersion: "5.0", maxVersion: "", priority: 100, translatorType: 4, browserSupport: "gcsibv", lastUpdated: "2021-10-23 00:00:00", cacheCode: true }; info.code = JSON.stringify(info, null, '\t') + "\n\n" + ` // asynchronous detectWeb async function detectWeb() { await doNothing(); return 'book'; } function doNothing() { return new Promise(resolve => resolve('nothing')); } // synchronous doWeb function doWeb(doc) { let item = new Zotero.Item('webpage'); item.title = 'Untitled'; item.complete(); } `; var translator = new Zotero.Translator(info); var translate = new Zotero.Translate.Web(); var provider = Zotero.Translators.makeTranslatorProvider({ get: function (translatorID) { if (translatorID == info.translatorID) { return translator; } return false; }, getAllForType: async function (type) { var translators = []; if (type == 'web') { translators.push(translator); } return translators; } }); translate.setTranslatorProvider(provider); translate.setDocument(doc); var translators = await translate.getTranslators(); assert.equal(translators.length, 1); assert.equal(translators[0].translatorID, info.translatorID); var newItems = await translate.translate(); assert.equal(newItems.length, 1); assert.equal(newItems[0].getField('title'), 'Untitled'); }); it('should support async doWeb', async function () { var translate = new Zotero.Translate.Web(); translate.setDocument(doc); translate.setTranslator( buildDummyTranslator( 4, ` function detectWeb() {} async function doWeb(doc) { let item = new Zotero.Item('webpage'); let otherDoc = await requestDocument('${htmlURL}'); item.title = otherDoc.title; let { status } = await request('${htmlURL}'); item.abstractNote = 'Status ' + status; item.complete(); let json = await requestJSON('${jsonURL}'); if (json.success) { item = new Zotero.Item('webpage'); item.title = 'JSON Test'; item.complete(); } } ` ) ); var newItems = await translate.translate(); assert.equal(newItems.length, 2); var item = newItems[0]; assert.equal(item.getField('title'), 'Test'); assert.equal(item.getField('abstractNote'), 'Status 200'); var item = newItems[1]; assert.equal(item.getField('title'), 'JSON Test'); }); it('should not fail translation on a non-200 status code', async function () { var translate = new Zotero.Translate.Web(); translate.setDocument(doc); translate.setTranslator( buildDummyTranslator( 4, `function detectWeb() {} async function doWeb(doc) { await request('${notFoundURL}').catch(e => {}); let item = new Zotero.Item('webpage'); item.title = 'Nothing'; item.complete(); }` ) ); var newItems = await translate.translate(); assert.equal(newItems.length, 1); var item = newItems[0]; assert.equal(item.getField('title'), 'Nothing'); }); }); 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 = 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 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(async function () { itemWithAutomaticTag = new Zotero.Item('journalArticle'); itemWithAutomaticTag.addTag('automatic tag', 0); await itemWithAutomaticTag.save(); itemWithManualTag = new Zotero.Item('journalArticle'); itemWithManualTag.addTag('manual tag', 1); await itemWithManualTag.save(); itemWithMultipleTags = new Zotero.Item('journalArticle'); itemWithMultipleTags.addTag('tag1', 0); itemWithMultipleTags.addTag('tag2', 1); await itemWithMultipleTags.save(); }); let legacyMode = [false, true]; for (let i=0; i 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"; await collections[0].save(); await collections[1].save(); collections[2].parentID = collections[0].id; collections[3].parentID = collections[1].id; await collections[2].save(); await collections[3].save(); await collections[0].addItems([items[1].id, items[2].id]); await collections[1].addItem(items[2].id); await 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(async function () { items = [ new Zotero.Item('journalArticle'), // Item with no relations new Zotero.Item('journalArticle'), // Bidirectional relations new Zotero.Item('journalArticle'), // between these items 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') ]; await Zotero.Promise.all(items.map(item => item.save())); await items[1].addRelatedItem(items[2]); await items[2].addRelatedItem(items[1]); await items[3].addRelatedItem(items[4]); await items[4].addRelatedItem(items[3]); await items[3].addRelatedItem(items[5]); await items[5].addRelatedItem(items[3]); }); 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 lists one relations predicate'); assert.lengthOf(translatorItem.relations['dc:relation'], 1, 'item that is the subject of a single relation lists one "dc:relation" object'); assert.equal(translatorItem.relations['dc:relation'][0], Zotero.URI.getItemURI(items[2]), 'item that is the subject of a single relation identifies correct object URI'); // We currently assign these bidirectionally above, so this is a bit redundant 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.lengthOf(translatorItem.relations['dc:relation'], 1, 'item that is the object of a single relation lists one "dc:relation" object'); assert.equal(translatorItem.relations['dc:relation'][0], 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.lengthOf(translatorItem.relations['dc:relation'], 1, 'item that is the object of one relation from item with two relations lists one "dc:relation" object'); assert.equal(translatorItem.relations['dc:relation'][0], 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(async function () { relatedItem = new Zotero.Item('journalArticle'); await relatedItem.save(); note = new Zotero.Item('note'); note.setNote('Note'); note.addTag('automaticTag', 0); note.addTag('manualTag', 1); note.addRelatedItem(relatedItem); await note.save(); relatedItem.addRelatedItem(note); await relatedItem.save(); collection = new Zotero.Collection; collection.name = 'test'; await collection.save(); await collection.addItem(note.id); }); let legacyMode = [false, true]; for (let i=0; i item.save())); collection = new Zotero.Collection; collection.name = 'test'; await collection.save(); await collection.addItem(items[0].id); await collection.addItem(items[1].id); note = new Zotero.Item('note'); note.setNote('Note'); note.addTag('automaticTag', 0); note.addTag('manualTag', 1); await note.save(); note.addRelatedItem(relatedItem); relatedItem.addRelatedItem(note); await note.save(); await relatedItem.save(); }); let legacyMode = [false, true]; for (let i=0; i