From 2827f70daaf571765a37a5f4a1ddda8f13a1b76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Tue, 6 Jun 2017 12:07:12 +0300 Subject: [PATCH] Option to delay updating citation in document. The checkbox in doc prefs is hidden until an update takes 5s or longer after which the user is prompted to enable delaying. --- chrome/content/zotero/bibliography.js | 21 ++ .../integration/integrationDocPrefs.xul | 8 +- chrome/content/zotero/xpcom/integration.js | 213 ++++++++++++------ chrome/locale/en-US/zotero/zotero.dtd | 4 + chrome/locale/en-US/zotero/zotero.properties | 1 + test/tests/integrationTest.js | 131 ++++++++++- 6 files changed, 308 insertions(+), 70 deletions(-) diff --git a/chrome/content/zotero/bibliography.js b/chrome/content/zotero/bibliography.js index 3e83cf4ca6..7702208d8a 100644 --- a/chrome/content/zotero/bibliography.js +++ b/chrome/content/zotero/bibliography.js @@ -167,10 +167,23 @@ var Zotero_File_Interface_Bibliography = new function() { document.getElementById("automaticJournalAbbreviations-checkbox").checked = true; } } + if (document.getElementById("delayCitationUpdates-checkbox")) { + if (_io.delayCitationUpdates) { + document.getElementById("delayCitationUpdates-checkbox").checked = true; + } + } // set style to false, in case this is cancelled _io.style = false; }); + + this.openHelpLink = function() { + var url = "https://www.zotero.org/support/word_processor_plugin_usage"; + var win = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator) + .getMostRecentWindow("navigator:browser"); + Zotero.launchURL(url); + }; /* * Called when locale is changed @@ -211,6 +224,13 @@ var Zotero_File_Interface_Bibliography = new function() { document.getElementById("automaticJournalAbbreviations-vbox").hidden = !selectedStyleObj.usesAbbreviation; } + // Hide the delayCitationUpdates checkbox before the prompt is shown + document.getElementById("delayCitationUpdates-vbox").hidden = _io.dontAskDelayCitationUpdates == undefined; + // Highlight delay citations checkbox after displaying the alert + // NOTE: Currently unused + if (_io.highlightDelayCitations) { + document.getElementById("delayCitationUpdates-vbox").style.border = "1px dashed #e52e2e" + } // // For bibliography.xul @@ -266,6 +286,7 @@ var Zotero_File_Interface_Bibliography = new function() { } _io.useEndnotes = document.getElementById("displayAs").selectedIndex; _io.fieldType = (document.getElementById("formatUsing").selectedIndex == 0 ? _io.primaryFieldType : _io.secondaryFieldType); + _io.delayCitationUpdates = document.getElementById("delayCitationUpdates-checkbox").checked; } // remember style and locale if user selected these explicitly diff --git a/chrome/content/zotero/integration/integrationDocPrefs.xul b/chrome/content/zotero/integration/integrationDocPrefs.xul index 50f90205e9..0d6a561f3c 100644 --- a/chrome/content/zotero/integration/integrationDocPrefs.xul +++ b/chrome/content/zotero/integration/integrationDocPrefs.xul @@ -31,10 +31,11 @@ &zotero.integration.prefs.automaticJournalAbbeviations.caption; + + + + &zotero.integration.prefs.delayCitationUpdates.description; + \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js index 9e7b4905a4..d386fa2dce 100644 --- a/chrome/content/zotero/xpcom/integration.js +++ b/chrome/content/zotero/xpcom/integration.js @@ -51,6 +51,8 @@ const INTEGRATION_TYPE_ITEM = 1; const INTEGRATION_TYPE_BIBLIOGRAPHY = 2; const INTEGRATION_TYPE_TEMP = 3; +const DELAY_CITATIONS_PROMPT_TIMEOUT = 5/*seconds*/; + Zotero.Integration = new function() { Components.utils.import("resource://gre/modules/Services.jsm"); @@ -212,18 +214,25 @@ Zotero.Integration = new function() { return; } inProgress = true; - - var application = Zotero.Integration.getApplication(agent, command, docId); - + Zotero.debug(`Integration: ${agent}-${command}${docId ? `:'${docId}'` : ''} invoked`) + + var startTime = (new Date()).getTime(); + // Try to execute the command; otherwise display an error in alert service or word processor // (depending on what is possible) - Zotero.Integration.currentDoc = document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument()); - Zotero.Integration.currentSession = session = yield Zotero.Integration.getSession(application, document); - // TODO: this is pretty awful - session.fields = new Zotero.Integration.Fields(session, document); - session._doc = document; try { + // Word for windows throws RPC_E_CANTCALLOUT_ININPUTSYNCCALL if we invoke an OLE call in the + // current event loop (which.. who would have guessed would be the case?) + yield Zotero.Promise.delay(); + var application = Zotero.Integration.getApplication(agent, command, docId); + + Zotero.Integration.currentDoc = document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument()); + Zotero.Integration.currentSession = session = yield Zotero.Integration.getSession(application, document); + // TODO: this is pretty awful + session.fields = new Zotero.Integration.Fields(session, document); + session._doc = document; yield (new Zotero.Integration.Interface(application, document, session))[command](); + document.setDocumentData(session.data.serialize()); } catch (e) { if(!(e instanceof Zotero.Exception.UserCancelled)) { @@ -262,12 +271,16 @@ Zotero.Integration = new function() { } finally { Zotero.logError(e); } + } else { + // If user cancels we should still write the currently assigned session ID + document.setDocumentData(session.data.serialize()); } } finally { + var diff = ((new Date()).getTime() - startTime)/1000; + Zotero.debug(`Integration: ${agent}-${command}${docId ? `:'${docId}'` : ''} complete in ${diff}s`) if (document) { try { - document.setDocumentData(session.data.serialize()); document.cleanup(); document.activate(); @@ -377,19 +390,20 @@ Zotero.Integration = new function() { throw new Zotero.Exception.Alert("integration.error.fieldTypeMismatch", [], "integration.error.title"); } - - if (Zotero.Integration.sessions[data.sessionID]) { - // If communication occured with this document since restart - return Zotero.Integration.sessions[data.sessionID]; - } + + session = Zotero.Integration.sessions[data.sessionID]; + } + if (!session) { + session = new Zotero.Integration.Session(doc, app); + session.reload = true; } - session = new Zotero.Integration.Session(doc, app); try { yield session.setData(data); } catch(e) { // make sure style is defined if (e instanceof Zotero.Exception.Alert && e.name === "integration.error.invalidStyle") { if (data.style.styleID) { + session.reload = true; let trustedSource = /^https?:\/\/(www\.)?(zotero\.org|citationstyles\.org)/.test(data.style.styleID); let errorString = Zotero.getString("integration.error.styleMissing", data.style.styleID); if (trustedSource || @@ -422,7 +436,6 @@ Zotero.Integration = new function() { throw e; } } - session.reload = true; return session; }); @@ -461,7 +474,12 @@ Zotero.Integration.Interface.prototype.addCitation = Zotero.Promise.coroutine(fu let [idx, field, citation] = yield this._session.fields.addEditCitation(null); yield this._session.addCitation(idx, field.noteIndex, citation); - return this._session.fields.updateDocument(FORCE_CITATIONS_FALSE, false, false); + + if (this._session.data.prefs.delayCitationUpdates) { + return this._session.writeDelayedCitation(idx, field, citation); + } else { + return this._session.fields.updateDocument(FORCE_CITATIONS_FALSE, false, false); + } }); /** @@ -487,7 +505,11 @@ Zotero.Integration.Interface.prototype.addEditCitation = Zotero.Promise.coroutin let [idx, field, citation] = yield this._session.fields.addEditCitation(docField); yield this._session.addCitation(idx, field.noteIndex, citation); - return this._session.fields.updateDocument(FORCE_CITATIONS_FALSE, false, false); + if (this._session.data.prefs.delayCitationUpdates) { + return this._session.writeDelayedCitation(idx, field, citation); + } else { + return this._session.fields.updateDocument(FORCE_CITATIONS_FALSE, false, false); + } }); /** @@ -588,14 +610,12 @@ Zotero.Integration.Interface.prototype.addEditBibliography = Zotero.Promise.coro * Updates the citation data for all citations and bibliography entries. * @return {Promise} */ -Zotero.Integration.Interface.prototype.refresh = function() { - var me = this; - return this._session.init(true, false).then(function() { - // Send request, forcing update of citations and bibliography - return me._session.fields.updateSession(FORCE_CITATIONS_REGENERATE).then(function() { - return me._session.fields.updateDocument(FORCE_CITATIONS_REGENERATE, true, false); - }); - }); +Zotero.Integration.Interface.prototype.refresh = async function() { + await this._session.init(true, false) + + this._session.reload = this._session.data.prefs.delayCitationUpdates; + await this._session.fields.updateSession(FORCE_CITATIONS_REGENERATE) + await this._session.fields.updateDocument(FORCE_CITATIONS_REGENERATE, true, false); } /** @@ -898,11 +918,26 @@ Zotero.Integration.Fields.prototype._processFields = Zotero.Promise.coroutine(fu */ Zotero.Integration.Fields.prototype.updateDocument = Zotero.Promise.coroutine(function* (forceCitations, forceBibliography, ignoreCitationChanges) { - // Iterate through citations, yielding for UI updates - yield Zotero.Promise.each(this._session._updateCitations(), () => {}); + var startTime = (new Date()).getTime(); - yield Zotero.Promise.each( - this._updateDocument(forceCitations, forceBibliography, ignoreCitationChanges), () => {}); + yield this._session._updateCitations() + yield this._updateDocument(forceCitations, forceBibliography, ignoreCitationChanges) + + var diff = ((new Date()).getTime() - startTime)/1000; + Zotero.debug(`Integration: updateDocument complete in ${diff}s`) + // If the update takes longer than 5s suggest delaying citation updates + if (diff > DELAY_CITATIONS_PROMPT_TIMEOUT && !this._session.data.prefs.dontAskDelayCitationUpdates && !this._session.data.prefs.delayCitationUpdates) { + this._doc.activate(); + var result = this._doc.displayAlert(Zotero.getString('integration.delayCitationUpdates.alert'), + DIALOG_ICON_WARNING, DIALOG_BUTTONS_YES_NO_CANCEL); + if (result == 2) { + this._session.data.prefs.delayCitationUpdates = true; + } + if (result) { + this._session.data.prefs.dontAskDelayCitationUpdates = true; + // yield this._session.setDocPrefs(true); + } + } }); /** @@ -912,7 +947,7 @@ Zotero.Integration.Fields.prototype.updateDocument = Zotero.Promise.coroutine(fu * @param {Boolean} [ignoreCitationChanges] Whether to ignore changes to citations that have been * modified since they were created, instead of showing a warning */ -Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, forceBibliography, +Zotero.Integration.Fields.prototype._updateDocument = async function(forceCitations, forceBibliography, ignoreCitationChanges) { if(this.progressCallback) { var nFieldUpdates = Object.keys(this._session.updateIndices).length; @@ -929,34 +964,37 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, } catch(e) { Zotero.logError(e); } - yield; } + // Jump to next event loop step for UI updates + await Zotero.Promise.delay(); var citation = this._session.citationsByIndex[i]; let citationField = citation._field; - if(!citation.properties.dontUpdate) { - var formattedCitation = citation.properties.custom + if (!citation.properties.dontUpdate) { + var formattedCitation = citation.properties.formattedCitation && citation.properties.custom ? citation.properties.custom : citation.text; + var plainCitation = citation.properties.plainCitation && citationField.text; - if(forceCitations === FORCE_CITATIONS_RESET_TEXT - || citation.properties.formattedCitation !== formattedCitation) { - // Check if citation has been manually modified - if(!ignoreCitationChanges && citation.properties.plainCitation) { - var plainCitation = citationField.text; - if(plainCitation !== citation.properties.plainCitation) { - // Citation manually modified; ask user if they want to save changes - Zotero.debug("[_updateDocument] Attempting to update manually modified citation.\n" - + "Original: " + citation.properties.plainCitation + "\n" - + "Current: " + plainCitation - ); - citationField.select(); - var result = this._doc.displayAlert( - Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"), - DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO); - if(result) { - citation.properties.dontUpdate = true; - } + // If we're not specifically *not* trying to regen text + if (forceCitations != FORCE_CITATIONS_FALSE + // Or metadata has changed thus changing the formatted citation + || (citation.properties.formattedCitation !== formattedCitation) + // Or we shouldn't ignore citation changes and the citation text has changed + || (!ignoreCitationChanges && plainCitation !== citation.properties.plainCitation)) { + + if (plainCitation !== citation.properties.plainCitation) { + // Citation manually modified; ask user if they want to save changes + Zotero.debug("[_updateDocument] Attempting to update manually modified citation.\n" + + "Original: " + citation.properties.plainCitation + "\n" + + "Current: " + plainCitation + ); + citationField.select(); + var result = this._doc.displayAlert( + Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"), + DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO); + if (result) { + citation.properties.dontUpdate = true; } } @@ -972,8 +1010,10 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, citation.properties.formattedCitation = formattedCitation; citation.properties.plainCitation = citationField.getText(); - // But we still need writeToDoc to trigger RTF write for LO (see comment in writeToDoc()) - citationField.text = formattedCitation; + if (isRich) { + // But we still need writeToDoc to trigger RTF write for LO (see comment in writeToDoc()) + citationField.text = formattedCitation; + } } } } @@ -1027,8 +1067,9 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, } catch(e) { Zotero.logError(e); } - yield; } + // Jump to next event loop step for UI updates + await Zotero.Promise.delay(); if (bibliographyText) { field.text = bibliographyText; @@ -1081,14 +1122,17 @@ Zotero.Integration.Fields.prototype.addEditCitation = Zotero.Promise.coroutine(f } }); - var citationsByItemIDPromise = fieldIndexPromise.then(function() { - return this.updateSession(FORCE_CITATIONS_FALSE); - }.bind(this)).then(function() { - return this._session.citationsByItemID; - }.bind(this)); + var citationsByItemIDPromise; + if (this._session.data.prefs.delayCitationUpdates) { + citationsByItemIDPromise = Zotero.Promise.resolve(this._session.citationsByItemID); + } else { + citationsByItemIDPromise = fieldIndexPromise.then(function() { + return this.updateSession(FORCE_CITATIONS_FALSE); + }.bind(this)).then(function() { + return this._session.citationsByItemID; + }.bind(this)); + } - // Required for preview fn - citation.properties.noteIndex = field.noteIndex; var previewFn = Zotero.Promise.coroutine(function* (citation) { let idx = yield fieldIndexPromise; yield citationsByItemIDPromise; @@ -1352,7 +1396,7 @@ Zotero.Integration.Session.prototype.setData = Zotero.Promise.coroutine(function * if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was * cancelled. */ -Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(function* () { +Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(function* (highlightDelayCitations=false) { var io = new function() { this.wrappedJSObject = this; }; io.primaryFieldType = this.primaryFieldType; io.secondaryFieldType = this.secondaryFieldType; @@ -1362,6 +1406,9 @@ Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(func io.locale = this.data.style.locale; io.useEndnotes = this.data.prefs.noteType == 0 ? 0 : this.data.prefs.noteType-1; io.fieldType = this.data.prefs.fieldType; + io.delayCitationUpdates = this.data.prefs.delayCitationUpdates; + io.dontAskDelayCitationUpdates = this.data.prefs.dontAskDelayCitationUpdates; + io.highlightDelayCitations = highlightDelayCitations; io.automaticJournalAbbreviations = this.data.prefs.automaticJournalAbbreviations; io.requireStoreReferences = !Zotero.Utilities.isEmpty(this.embeddedItems); } @@ -1380,8 +1427,10 @@ Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(func data.sessionID = oldData.sessionID; data.style.styleID = io.style; data.style.locale = io.locale; + data.prefs = oldData ? Object.assign({}, oldData.prefs) : {}; data.prefs.fieldType = io.fieldType; data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations; + data.prefs.delayCitationUpdates = io.delayCitationUpdates var forceStyleReset = oldData && ( @@ -1396,9 +1445,11 @@ Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(func if (!oldData || oldData.style.styleID != data.style.styleID || oldData.prefs.noteType != data.prefs.noteType || oldData.prefs.fieldType != data.prefs.fieldType + || (!data.prefs.delayCitationUpdates && oldData.prefs.delayCitationUpdates != data.prefs.delayCitationUpdates) || oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) { // This will cause us to regenerate all citations this.regenAll = true; + this.reload = true; } return oldData || null; @@ -1450,7 +1501,6 @@ Zotero.Integration.Session.prototype.addCitation = Zotero.Promise.coroutine(func Zotero.debug("Integration: "+citation.citationID+" ("+index+") needs new citationID"); citation.citationID = Zotero.randomString(); } - this.newIndices[index] = true; this.updateIndices[index] = true; } Zotero.debug("Integration: Adding citationID "+citation.citationID); @@ -1468,7 +1518,7 @@ Zotero.Integration.Session.prototype.getCiteprocLists = function() { /** * Updates the list of citations to be serialized to the document */ -Zotero.Integration.Session.prototype._updateCitations = function* () { +Zotero.Integration.Session.prototype._updateCitations = async function () { Zotero.debug("Integration: Indices of new citations"); Zotero.debug(Object.keys(this.newIndices)); Zotero.debug("Integration: Indices of updated citations"); @@ -1478,6 +1528,8 @@ Zotero.Integration.Session.prototype._updateCitations = function* () { for (let indexList of [this.newIndices, this.updateIndices]) { for (let index in indexList) { + // Jump to next event loop step for UI updates + await Zotero.Promise.delay(); index = parseInt(index); var citation = this.citationsByIndex[index]; @@ -1503,9 +1555,7 @@ Zotero.Integration.Session.prototype._updateCitations = function* () { this.citationsByIndex[idx].text = text; } - // Yield for UI updates delete this.newIndices[index]; - yield; } } } @@ -1531,6 +1581,32 @@ Zotero.Integration.Session.prototype.restoreProcessorState = function() { this.style.rebuildProcessorState(citations, 'rtf', uncited); } + +Zotero.Integration.Session.prototype.writeDelayedCitation = Zotero.Promise.coroutine(function* (idx, field, citation) { + try { + var text = citation.properties.custom || this.style.previewCitationCluster(citation, [], [], "rtf"); + } catch(e) { + throw e; + } + text = `\\uldash{${text}}`; + + // Make sure we'll prompt for manually edited citations + if(!citation.properties.dontUpdate) { + // setText and getText here bypass the setter/getter abstraction + text = '{\\rtf' + text + '}'; + field.setText(text, true); + field.text = text; + + citation.properties.formattedCitation = text; + citation.properties.plainCitation = field.getText(); + } + + field.code = citation.serialize(); + field.text = text; + field.writeToDoc(); +}); + + /** * Edits integration bibliography * @param {Zotero.Integration.Bibliography} bibliography @@ -1681,7 +1757,8 @@ Zotero.Integration.DocumentData.prototype.serialize = function() { } // Otherwise default to XML for now var prefs = ""; - for(var pref in this.prefs) { + for (var pref in this.prefs) { + if (!this.prefs[pref]) continue; prefs += ``; } diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd index c0c68c64ed..b75631bb52 100644 --- a/chrome/locale/en-US/zotero/zotero.dtd +++ b/chrome/locale/en-US/zotero/zotero.dtd @@ -231,6 +231,10 @@ + + + + diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index 680e10f89f..fe621bbb9a 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -870,6 +870,7 @@ integration.corruptBibliography.description = All items cited in the text will a integration.citationChanged = You have modified this citation since Zotero generated it. Do you want to keep your modifications and prevent future updates? integration.citationChanged.description = Clicking "Yes" will prevent Zotero from updating this citation if you add additional citations, switch styles, or modify the item to which it refers. Clicking "No" will erase your changes. integration.citationChanged.edit = You have modified this citation since Zotero generated it. Editing will clear your modifications. Do you want to continue? +integration.delayCitationUpdates.alert = Updating citations in this document is taking a long time. Would you like to delay citation updates until manual refresh?\n\nYou can change this setting later in the document preferences. styles.install.title = Install Style styles.install.unexpectedError = An unexpected error occurred while installing "%1$S" diff --git a/test/tests/integrationTest.js b/test/tests/integrationTest.js index 4ef509f475..ffe221a2a9 100644 --- a/test/tests/integrationTest.js +++ b/test/tests/integrationTest.js @@ -292,7 +292,10 @@ describe("Zotero.Integration", function () { testItems = []; for (let i = 0; i < 5; i++) { - testItems.push(yield createDataObject('item', {libraryID: Zotero.Libraries.userLibraryID})); + let testItem = yield createDataObject('item', {libraryID: Zotero.Libraries.userLibraryID}); + testItem.setField('title', `title${1}`); + testItem.setCreator(0, {creatorType: 'author', name: `Author No${i}`}); + testItems.push(testItem); } setAddEditItems(testItems[0]); @@ -492,6 +495,132 @@ describe("Zotero.Integration", function () { getCiteprocBibliographySpy.restore(); }); + + describe('when original citation text has been modified', function() { + var displayAlertStub; + before(function* () { + displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').returns(0); + }); + beforeEach(function() { + displayAlertStub.reset(); + }); + after(function() { + displayAlertStub.restore(); + }); + it('should keep modification if "Cancel" selected in editCitation triggered alert', async function () { + await insertMultipleCitations.call(this); + var docID = this.test.fullTitle(); + var doc = applications[docID].doc; + + doc.fields[0].text = "modified"; + sinon.stub(doc, 'cursorInField').returns(doc.fields[0]); + sinon.stub(doc, 'canInsertField').returns(false); + + await execCommand('addEditCitation', docID); + assert.equal(doc.fields.length, 2); + assert.equal(doc.fields[0].text, "modified"); + }); + it('should display citation dialog if "OK" selected in editCitation triggered alert', async function () { + await insertMultipleCitations.call(this); + var docID = this.test.fullTitle(); + var doc = applications[docID].doc; + + let origText = doc.fields[0].text; + doc.fields[0].text = "modified"; + // Return OK + displayAlertStub.returns(1); + sinon.stub(doc, 'cursorInField').returns(doc.fields[0]); + sinon.stub(doc, 'canInsertField').returns(false); + setAddEditItems(testItems[0]); + + await execCommand('addEditCitation', docID); + assert.isTrue(displayAlertStub.called); + assert.equal(doc.fields.length, 2); + assert.equal(doc.fields[0].text, origText); + }); + it('should set dontUpdate: true if "yes" selected in refresh prompt', async function() { + await insertMultipleCitations.call(this); + var docID = this.test.fullTitle(); + var doc = applications[docID].doc; + + var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize(); + assert.isNotOk(citation.properties.dontUpdate); + doc.fields[0].text = "modified"; + // Return Yes + displayAlertStub.returns(1); + + await execCommand('refresh', docID); + assert.isTrue(displayAlertStub.called); + assert.equal(doc.fields.length, 2); + assert.equal(doc.fields[0].text, "modified"); + var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize(); + assert.isOk(citation.properties.dontUpdate); + }); + it('should reset citation text if "no" selected in refresh prompt', async function() { + await insertMultipleCitations.call(this); + var docID = this.test.fullTitle(); + var doc = applications[docID].doc; + + var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize(); + assert.isNotOk(citation.properties.dontUpdate); + let origText = doc.fields[0].text; + doc.fields[0].text = "modified"; + // Return No + displayAlertStub.returns(0); + + await execCommand('refresh', docID); + assert.isTrue(displayAlertStub.called); + assert.equal(doc.fields.length, 2); + assert.equal(doc.fields[0].text, origText); + var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize(); + assert.isNotOk(citation.properties.dontUpdate); + }); + }); + + describe('when delayCitationUpdates is set', function() { + it('should insert a citation with wave underlining', function* (){ + yield insertMultipleCitations.call(this); + var docID = this.test.fullTitle(); + var doc = applications[docID].doc; + var data = new Zotero.Integration.DocumentData(doc.data); + data.prefs.delayCitationUpdates = true; + doc.data = data.serialize(); + + var setTextSpy = sinon.spy(DocumentPluginDummy.Field.prototype, 'setText'); + setAddEditItems(testItems[3]); + yield execCommand('addEditCitation', docID); + assert.isTrue(setTextSpy.lastCall.args[0].includes('\\uldash')); + + setTextSpy.restore(); + }); + + it('should not write to any other fields besides the one being updated', function* () { + yield insertMultipleCitations.call(this); + var docID = this.test.fullTitle(); + var doc = applications[docID].doc; + var data = new Zotero.Integration.DocumentData(doc.data); + data.prefs.delayCitationUpdates = true; + doc.data = data.serialize(); + + var setTextSpy = sinon.spy(DocumentPluginDummy.Field.prototype, 'setText'); + var setCodeSpy = sinon.spy(DocumentPluginDummy.Field.prototype, 'setCode'); + + setAddEditItems(testItems[3]); + yield execCommand('addEditCitation', docID); + var field = setTextSpy.firstCall.thisValue; + + for (let i = 0; i < setTextSpy.callCount; i++) { + assert.isTrue(field.equals(setTextSpy.getCall(i).thisValue)); + } + + for (let i = 0; i < setCodeSpy.callCount; i++) { + assert.isTrue(field.equals(setCodeSpy.getCall(i).thisValue)); + } + + setTextSpy.restore(); + setCodeSpy.restore(); + }) + }); }); describe('#addEditBibliography', function() {