diff --git a/chrome/content/zotero/bibliography.js b/chrome/content/zotero/bibliography.js index 1d7d4e9c5..5e198255f 100644 --- a/chrome/content/zotero/bibliography.js +++ b/chrome/content/zotero/bibliography.js @@ -45,7 +45,7 @@ var Zotero_File_Interface_Bibliography = new function() { * Initialize some variables and prepare event listeners for when chrome is done * loading */ - this.init = function () { + this.init = Zotero.Promise.coroutine(function* () { // Set font size from pref // Affects bibliography.xul and integrationDocPrefs.xul var bibContainer = document.getElementById("zotero-bibliography-container"); @@ -67,21 +67,22 @@ var Zotero_File_Interface_Bibliography = new function() { _io.style = Zotero.Prefs.get("export.lastStyle"); } + // Initialize styles + yield Zotero.Styles.init(); + // add styles to list + var styles = Zotero.Styles.getVisible(); - var index = 0; - var nStyles = styles.length; var selectIndex = null; - for(var i=0; i=0; i--) { fields[i].removeCode(); @@ -1301,8 +1320,8 @@ Zotero.Integration.Fields.prototype.addField = function(note) { var field = this._doc.cursorInField(this._session.data.prefs['fieldType']); if(field) { if(!this._doc.displayAlert(Zotero.getString("integration.replace"), - Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP, - Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) { + DIALOG_ICON_STOP, + DIALOG_BUTTONS_OK_CANCEL)) { return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("inserting citation")); } } @@ -1607,8 +1626,7 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, field.select(); var result = this._doc.displayAlert( Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"), - Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION, - Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO); + DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO); if(result) { citation.properties.dontUpdate = true; } @@ -1752,8 +1770,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = Zotero.Promise.coroutine(f + "Current: " + field.getText() ); if(!this._doc.displayAlert(Zotero.getString("integration.citationChanged.edit"), - Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING, - Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) { + DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL)) { throw new Zotero.Exception.UserCancelled("editing citation"); } } @@ -2066,30 +2083,30 @@ Zotero.Integration.Session.prototype.resetRequest = function(doc) { * regardless of whether it has changed. This is desirable if the * automaticJournalAbbreviations or locale has changed. */ -Zotero.Integration.Session.prototype.setData = function(data, resetStyle) { +Zotero.Integration.Session.prototype.setData = Zotero.Promise.coroutine(function *(data, resetStyle) { var oldStyle = (this.data && this.data.style ? this.data.style : false); this.data = data; - if(data.style.styleID && (!oldStyle || oldStyle.styleID != data.style.styleID || resetStyle)) { + if (data.style.styleID && (!oldStyle || oldStyle.styleID != data.style.styleID || resetStyle)) { this.styleID = data.style.styleID; try { + yield Zotero.Styles.init(); var getStyle = Zotero.Styles.get(data.style.styleID); data.style.hasBibliography = getStyle.hasBibliography; this.style = getStyle.getCiteProc(data.style.locale, data.prefs.automaticJournalAbbreviations); this.style.setOutputFormat("rtf"); this.styleClass = getStyle.class; this.dateModified = new Object(); - } catch(e) { + } catch (e) { Zotero.logError(e); - data.style.styleID = undefined; throw new Zotero.Exception.Alert("integration.error.invalidStyle"); } return true; - } else if(oldStyle) { + } else if (oldStyle) { data.style = oldStyle; } return false; -} +}); /** * Displays a dialog to set document preferences @@ -2097,12 +2114,12 @@ Zotero.Integration.Session.prototype.setData = function(data, resetStyle) { * if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was * cancelled. */ -Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldType, secondaryFieldType) { +Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(function* (doc, primaryFieldType, secondaryFieldType) { var io = new function() { this.wrappedJSObject = this; }; - if(this.data) { + if (this.data) { io.style = this.data.style.styleID; io.locale = this.data.style.locale; io.useEndnotes = this.data.prefs.noteType == 0 ? 0 : this.data.prefs.noteType-1; @@ -2114,45 +2131,43 @@ Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldTyp io.requireStoreReferences = !Zotero.Utilities.isEmpty(this.embeddedItems); } - var me = this; - return Zotero.Integration.displayDialog(doc, - 'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io) - .then(function() { - if (!io.style || !io.fieldType) { - throw new Zotero.Exception.UserCancelled("document preferences window"); - } - - // set data - var oldData = me.data; - var data = new Zotero.Integration.DocumentData(); - data.sessionID = oldData.sessionID; - data.style.styleID = io.style; - data.style.locale = io.locale; - data.prefs.fieldType = io.fieldType; - data.prefs.storeReferences = io.storeReferences; - data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations; - - var forceStyleReset = oldData - && ( - oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations - || oldData.style.locale != io.locale - ); - me.setData(data, forceStyleReset); + yield Zotero.Integration.displayDialog(doc, + 'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io); + + if (!io.style || !io.fieldType) { + throw new Zotero.Exception.UserCancelled("document preferences window"); + } + + // set data + var oldData = this.data; + var data = new Zotero.Integration.DocumentData(); + data.sessionID = oldData.sessionID; + data.style.styleID = io.style; + data.style.locale = io.locale; + data.prefs.fieldType = io.fieldType; + data.prefs.storeReferences = io.storeReferences; + data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations; + + var forceStyleReset = oldData + && ( + oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations + || oldData.style.locale != io.locale + ); + yield this.setData(data, forceStyleReset); - // need to do this after setting the data so that we know if it's a note style - me.data.prefs.noteType = me.style && me.styleClass == "note" ? io.useEndnotes+1 : 0; - - if(!oldData || oldData.style.styleID != data.style.styleID - || oldData.prefs.noteType != data.prefs.noteType - || oldData.prefs.fieldType != data.prefs.fieldType - || oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) { - // This will cause us to regenerate all citations - me.oldCitationIDs = {}; - } - - return oldData || null; - }); -} + // need to do this after setting the data so that we know if it's a note style + this.data.prefs.noteType = this.style && this.styleClass == "note" ? io.useEndnotes+1 : 0; + + if (!oldData || oldData.style.styleID != data.style.styleID + || oldData.prefs.noteType != data.prefs.noteType + || oldData.prefs.fieldType != data.prefs.fieldType + || oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) { + // This will cause us to regenerate all citations + this.oldCitationIDs = {}; + } + + return oldData || null; +}) /** * Reselects an item to replace a deleted item @@ -2348,9 +2363,9 @@ Zotero.Integration.Session.prototype.lookupItems = Zotero.Promise.coroutine(func this.updateIndices[index] = true; } } else { - if(citationItem.key) { + if(citationItem.key && citationItem.libraryID) { // DEBUG: why no library id? - zoteroItem = Zotero.Items.getByLibraryAndKey(0, citationItem.key); + zoteroItem = Zotero.Items.getByLibraryAndKey(citationItem.libraryID, citationItem.key); } else if(citationItem.itemID) { zoteroItem = Zotero.Items.get(citationItem.itemID); } else if(citationItem.id) { @@ -3082,9 +3097,9 @@ Zotero.Integration.DocumentData.prototype.unserialize = function(input) { "storeReferences":false}; if(prefParameters[2] == "note") { if(prefParameters[4] == "1" || prefParameters[4] == "True") { - this.prefs.noteType = Components.interfaces.zoteroIntegrationDocument.NOTE_ENDNOTE; + this.prefs.noteType = NOTE_ENDNOTE; } else { - this.prefs.noteType = Components.interfaces.zoteroIntegrationDocument.NOTE_FOOTNOTE; + this.prefs.noteType = NOTE_FOOTNOTE; } } else { this.prefs.noteType = 0; diff --git a/chrome/content/zotero/xpcom/style.js b/chrome/content/zotero/xpcom/style.js index bdd97be0a..f73a3c54a 100644 --- a/chrome/content/zotero/xpcom/style.js +++ b/chrome/content/zotero/xpcom/style.js @@ -49,7 +49,6 @@ Zotero.Styles = new function() { this.reinit = Zotero.Promise.coroutine(function* () { Zotero.debug("Initializing styles"); var start = new Date; - _initialized = true; // Upgrade style locale prefs for 4.0.27 var bibliographyLocale = Zotero.Prefs.get("export.bibliographyLocale"); @@ -114,6 +113,7 @@ Zotero.Styles = new function() { _renamedStyles = xmlhttp.response; } }) + _initialized = true; }); this.init = Zotero.lazy(this.reinit); @@ -190,7 +190,7 @@ Zotero.Styles = new function() { } return _styles[id] || false; - } + }; /** * Gets all visible styles @@ -238,24 +238,36 @@ Zotero.Styles = new function() { /** * Installs a style file, getting the contents of an nsIFile and showing appropriate * error messages - * @param {String|nsIFile} style An nsIFile representing a style on disk, or a string - * containing the style data + * @param {Object} style An object with one of the following properties + * - file: An nsIFile representing a style on disk + * - url: A url of the location of the style (local or remote) + * - string: A string containing the style data * @param {String} origin The origin of the style, either a filename or URL, to be * displayed in dialogs referencing the style * @param {Boolean} [silent=false] Skip prompts */ this.install = Zotero.Promise.coroutine(function* (style, origin, silent=false) { var styleTitle; + var warnDeprecated; + if (style instanceof Components.interfaces.nsIFile) { + warnDeprecated = true; + style = {file: style}; + } else if (typeof style == 'string') { + warnDeprecated = true; + style = {string: style}; + } + if (warnDeprecated) { + Zotero.debug("Zotero.Styles.install() now takes a style object as first argument -- update your code", 2); + } try { - if (style instanceof Components.interfaces.nsIFile) { - // handle nsIFiles - var url = Services.io.newFileURI(style); - var xmlhttp = yield Zotero.HTTP.request("GET", url.spec); - styleTitle = yield _install(xmlhttp.responseText, style.leafName, false, silent); - } else { - styleTitle = yield _install(style, origin, false, silent); + if (style.file) { + style.string = yield Zotero.File.getContentsAsync(style.file); } + else if (style.url) { + style.string = yield Zotero.File.getContentsFromURLAsync(style.url); + } + styleTitle = yield _install(style.string, origin, false, silent); } catch (error) { // Unless user cancelled, show an alert with the error diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index 896dc86a5..c3452564b 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -818,6 +818,7 @@ integration.error.noBibliography = The current bibliographic style does not def integration.error.deletePipe = The pipe that Zotero uses to communicate with the word processor could not be initialized. Would you like Zotero to attempt to correct this error? You will be prompted for your password. integration.error.invalidStyle = The style you have selected does not appear to be valid. If you have created this style yourself, please ensure that it passes validation as described at https://github.com/citation-style-language/styles/wiki/Validation. Alternatively, try selecting another style. integration.error.fieldTypeMismatch = Zotero cannot update this document because it was created by a different word processing application with an incompatible field encoding. In order to make a document compatible with both Word and LibreOffice, open the document in the word processor with which it was originally created and switch the field type to Bookmarks in the Zotero Document Preferences. +integration.error.styleMissing = The citation style used in this document is missing. Would you like to install it from %S? integration.replace = Replace this Zotero field? integration.missingItem.single = The highlighted citation no longer exists in your Zotero database. Do you want to select a substitute item? diff --git a/test/tests/data/cell.csl b/test/tests/data/cell.csl new file mode 100644 index 000000000..1262840da --- /dev/null +++ b/test/tests/data/cell.csl @@ -0,0 +1,116 @@ + + diff --git a/test/tests/integrationTests.js b/test/tests/integrationTests.js new file mode 100644 index 000000000..170950b5b --- /dev/null +++ b/test/tests/integrationTests.js @@ -0,0 +1,323 @@ +"use strict"; + +describe("Zotero.Integration", function () { + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + // Fully functional document plugin dummy based on word-for-windows-integration + // Functions should be stubbed when testing as needed + var DocumentPluginDummy = {}; + + DocumentPluginDummy.Application = function() { + this.doc = new DocumentPluginDummy.Document(); + this.primaryFieldType = "Field"; + this.secondaryFieldType = "Bookmark"; + this.fields = []; + }; + DocumentPluginDummy.Application.prototype = { + getActiveDocument: function() {return this.doc}, + getDocument: function() {return this.doc}, + QueryInterface: function() {return this}, + }; + + DocumentPluginDummy.Document = function() {this.fields = []}; + DocumentPluginDummy.Document.prototype = { + // Needs to be stubbed for expected return values depending on prompt type + // - Yes: 2, No: 1, Cancel: 0 + // - Yes: 1, No: 0 + // - Ok: 1, Cancel: 0 + // - Ok: 0 + displayAlert: () => 0, + activate: () => 0, + canInsertField: () => true, + cursorInField: () => false, + getDocumentData: function() {return this.data}, + setDocumentData: function(data) {this.data = data}, + insertField: function() { var field = new DocumentPluginDummy.Field(this); this.fields.push(field); return field }, + getFields: function() {return new DocumentPluginDummy.FieldEnumerator(this)}, + getFieldsAsync: function(fieldType, observer) { + observer.observe(this.getFields(fieldType), 'fields-available', null) + }, + setBibliographyStyle: () => 0, + convert: () => 0, + cleanup: () => 0, + complete: () => 0, + QueryInterface: function() {return this}, + }; + + DocumentPluginDummy.FieldEnumerator = function(doc) {this.doc = doc; this.idx = 0}; + DocumentPluginDummy.FieldEnumerator.prototype = { + hasMoreElements: function() {return this.idx < this.doc.fields.length;}, + getNext: function() {return this.doc.fields[this.idx++]}, + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports, + Components.interfaces.nsISimpleEnumerator]) + }; + + DocumentPluginDummy.Field = function(doc) { + this.doc = doc; + this.code = this.text = ''; + this.noteIndex = DocumentPluginDummy.Field.noteIndex++; + this.wrappedJSObject = this; + }; + DocumentPluginDummy.Field.noteIndex = 0; + DocumentPluginDummy.Field.prototype = { + delete: function() {this.doc.fields.filter((field) => field != this)}, + removeCode: function() {this.code = ""}, + select: () => 0, + setText: function(text) {this.text = text}, + getText: function() {return this.text}, + setCode: function(code) {this.code = code}, + getCode: function() {return this.code}, + equals: function(field) {return this == field}, + getNoteIndex: function() {return this.noteIndex}, + QueryInterface: function() {return this}, + }; + + for (let cls of ['Application', 'Document', 'FieldEnumerator', 'Field']) { + for (let methodName in DocumentPluginDummy[cls].prototype) { + if (methodName !== 'QueryInterface') { + let method = DocumentPluginDummy[cls].prototype[methodName]; + DocumentPluginDummy[cls].prototype[methodName] = function() { + try { + Zotero.debug(`DocumentPluginDummy: ${cls}.${methodName} invoked with args ${JSON.stringify(arguments)}`, 2); + } catch (e) { + Zotero.debug(`DocumentPluginDummy: ${cls}.${methodName} invoked with args ${arguments}`, 2); + } + var result = method.apply(this, arguments); + try { + Zotero.debug(`Result: ${JSON.stringify(result)}`, 2); + } catch (e) { + Zotero.debug(`Result: ${result}`, 2); + } + return result; + } + } + } + } + + var testItems; + var applications = {}; + var addEditCitationSpy, displayDialogStub; + var styleID = "http://www.zotero.org/styles/cell"; + var stylePath = OS.Path.join(getTestDataDirectory().path, 'cell.csl'); + + var commandList = [ + 'addCitation', 'editCitation', 'addEditCitation', + 'addBibliography', 'editBibliography', 'addEditBibliography', + 'refresh', 'removeCodes', 'setDocPrefs' + ]; + + function execCommand(command, docID) { + if (! commandList.includes(command)) { + throw new Error(`${command} is not a valid document command`); + } + if (typeof docID === "undefined") { + throw new Error(`docID cannot be undefined`) + } + return Zotero.Integration.execCommand("dummy", command, docID); + } + + var dialogResults = { + addCitationDialog: {}, + quickFormat: {}, + integrationDocPrefs: {}, + selectItemsDialog: {}, + editBibliographyDialog: {} + }; + + function initDoc(docID, options={}) { + applications[docID] = new DocumentPluginDummy.Application(); + var data = new Zotero.Integration.DocumentData(); + data.prefs = { + noteType: 0, + fieldType: "Field", + storeReferences: true, + automaticJournalAbbreviations: true + }; + data.style = {styleID, locale: 'en-US', hasBibliography: true, bibliographyStyleHasBeenSet: true}; + data.sessionID = Zotero.Utilities.randomString(10); + Object.assign(data, options); + applications[docID].getActiveDocument().setDocumentData(data.serializeXML()); + } + + function setDefaultIntegrationDocPrefs() { + dialogResults.integrationDocPrefs = { + style: "http://www.zotero.org/styles/cell", + locale: 'en-US', + fieldType: 'Field', + storeReferences: true, + automaticJournalAbbreviations: false, + useEndnotes: 0 + }; + } + setDefaultIntegrationDocPrefs(); + + function setAddEditItems(items) { + if (items.length == undefined) items = [items]; + dialogResults.quickFormat = function(doc, dialogName) { + var citationItems = items.map((i) => {return {id: i.id} }); + var field = doc.insertField(); + field.setCode('TEMP'); + var integrationDoc = addEditCitationSpy.lastCall.thisValue; + var fieldGetter = new Zotero.Integration.Fields(integrationDoc._session, integrationDoc._doc, () => 0); + var io = new Zotero.Integration.CitationEditInterface( + { citationItems, properties: {} }, + field, + fieldGetter, + integrationDoc._session + ); + io._acceptDeferred.resolve(); + return io; + } + } + + before(function* () { + yield Zotero.Styles.init(); + yield Zotero.Styles.install({file: stylePath}, styleID, true); + + testItems = []; + for (let i = 0; i < 5; i++) { + testItems.push(yield createDataObject('item', {libraryID: Zotero.Libraries.userLibraryID})); + } + setAddEditItems(testItems[0]); + + sinon.stub(Zotero.Integration, 'getApplication', function(agent, command, docID) { + if (!applications[docID]) { + applications[docID] = new DocumentPluginDummy.Application(); + } + return applications[docID]; + }); + + displayDialogStub = sinon.stub(Zotero.Integration, 'displayDialog', function(doc, dialogName, prefs, io) { + var ioResult = dialogResults[dialogName.substring(dialogName.lastIndexOf('/')+1, dialogName.length-4)]; + if (typeof ioResult == 'function') { + ioResult = ioResult(doc, dialogName); + } + Object.assign(io, ioResult); + return Zotero.Promise.resolve(); + }); + + addEditCitationSpy = sinon.spy(Zotero.Integration.Document.prototype, 'addEditCitation'); + }); + + after(function() { + Zotero.Integration.getApplication.restore(); + displayDialogStub.restore(); + addEditCitationSpy.restore(); + }); + + describe('Document', function() { + describe('#addEditCitation', function() { + var setDocumentDataSpy; + var docID = this.fullTitle(); + + before(function() { + setDocumentDataSpy = sinon.spy(DocumentPluginDummy.Document.prototype, 'setDocumentData'); + }); + + afterEach(function() { + setDocumentDataSpy.reset(); + }); + + after(function() { + setDocumentDataSpy.restore(); + }); + + it('should call doc.setDocumentData on a fresh document', function* () { + yield execCommand('addEditCitation', docID); + assert.isTrue(setDocumentDataSpy.calledOnce); + }); + + it('should not call doc.setDocumentData on subsequent invocations', function* () { + yield execCommand('addEditCitation', docID); + assert.isFalse(setDocumentDataSpy.called); + }); + + it('should not call doc.setDocumentData when document communicates for first time since restart, but has data', function* () { + Zotero.Integration.sessions = {}; + yield execCommand('addEditCitation', docID); + assert.isFalse(setDocumentDataSpy.called); + }); + + describe('when style used in the document does not exist', function() { + var docID = this.fullTitle(); + var displayAlertStub; + var style; + before(function* () { + displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').returns(0); + }); + + beforeEach(function() { + // 🦉birds? + style = {styleID: "http://www.example.com/csl/waterbirds", locale: 'en-US'}; + + // Make sure style not in library + try { + Zotero.Styles.get(style.styleID).remove(); + } catch (e) {} + initDoc(docID, {style}); + displayDialogStub.reset(); + displayAlertStub.reset(); + }); + + after(function* () { + displayAlertStub.restore(); + }); + + describe('when the style is not from a trusted source', function() { + it('should download the style and not call doc.setDocumentData if user clicks YES', function* () { + setDocumentDataSpy.reset(); + var styleInstallStub = sinon.stub(Zotero.Styles, "install").resolves(); + var style = Zotero.Styles.get(styleID); + var styleGetCalledOnce = false; + var styleGetStub = sinon.stub(Zotero.Styles, 'get', function() { + if (!styleGetCalledOnce) { + styleGetCalledOnce = true; + return false; + } + return style; + }); + displayAlertStub.returns(1); + yield execCommand('addEditCitation', docID); + assert.isTrue(displayAlertStub.calledOnce); + assert.isFalse(displayDialogStub.calledWith(applications[docID].doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul')); + assert.isTrue(styleInstallStub.calledOnce); + assert.isFalse(setDocumentDataSpy.called); + assert.isOk(Zotero.Styles.get(style.styleID)); + styleInstallStub.restore(); + styleGetStub.restore(); + }); + + it('should prompt with the document preferences dialog if user clicks NO', function* () { + displayAlertStub.returns(0); + yield execCommand('addEditCitation', docID); + assert.isTrue(displayAlertStub.calledOnce); + // Prefs to select a new style and quickFormat + assert.isTrue(displayDialogStub.calledTwice); + assert.isNotOk(Zotero.Styles.get(style.styleID)); + }); + }); + + it('should download the style without prompting if it is from zotero.org', function* (){ + initDoc(docID, {styleID: "http://www.zotero.org/styles/waterbirds", locale: 'en-US'}); + var styleInstallStub = sinon.stub(Zotero.Styles, "install").resolves(); + var style = Zotero.Styles.get(styleID); + var styleGetCalledOnce = false; + var styleGetStub = sinon.stub(Zotero.Styles, 'get', function() { + if (!styleGetCalledOnce) { + styleGetCalledOnce = true; + return false; + } + return style; + }); + displayAlertStub.returns(1); + yield execCommand('addEditCitation', docID); + assert.isFalse(displayAlertStub.called); + assert.isFalse(displayDialogStub.calledWith(applications[docID].doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul')); + assert.isTrue(styleInstallStub.calledOnce); + assert.isOk(Zotero.Styles.get(style.styleID)); + styleInstallStub.restore(); + styleGetStub.restore(); + }); + }); + }); + }); +}); diff --git a/test/tests/styleTest.js b/test/tests/styleTest.js new file mode 100644 index 000000000..198750380 --- /dev/null +++ b/test/tests/styleTest.js @@ -0,0 +1,45 @@ +"use strict"; + +describe("Zotero.Styles", function() { + var styleID = "http://www.zotero.org/styles/cell"; + var stylePath = OS.Path.join(getTestDataDirectory().path, 'cell.csl'); + var styleFile = Zotero.File.pathToFile(stylePath); + var style; + + before(function* () { + yield Zotero.Styles.init(); + style = yield Zotero.File.getContentsAsync(stylePath); + }); + + describe("Zotero.Styles.install", function() { + afterEach(`${styleID} style should be installed`, function* (){ + assert.isOk(Zotero.Styles.get(styleID)); + yield Zotero.Styles.get(styleID).remove(); + }); + + it("should install the style from string", function* () { + yield Zotero.Styles.install(style, styleID, true); + }); + + it("should install the style from nsIFile", function* () { + yield Zotero.Styles.install(styleFile, styleID, true); + }); + + it("should install the style from url", function* () { + var getContentsFromURLAsync = Zotero.File.getContentsFromURLAsync; + sinon.stub(Zotero.File, 'getContentsFromURLAsync', function(style) { + if (style.url == styleID) { + return Zotero.Promise.resolve(style); + } else { + return getContentsFromURLAsync.apply(Zotero.File, arguments); + } + }); + yield Zotero.Styles.install({url: styleID}, styleID, true); + Zotero.File.getContentsFromURLAsync.restore(); + }); + + it("should install the style from file path", function* () { + yield Zotero.Styles.install({file: stylePath}, styleID, true); + }) + }); +});