commit 02c43c3643c5059d2a42de8543bd3e488d2a1ba6
parent 269a250b4f950bf917c3a680f1d2c5aad2c75cd5
Author: Adomas VenĨkauskas <adomas.ven@gmail.com>
Date: Thu, 6 Apr 2017 14:19:25 +0300
Add integrationTests.js
Contains a dummy doc plugin, which is useful for:
- Testing integration.js functionality
- Serving as succint documentation for development of new integration
plugins
Diffstat:
3 files changed, 286 insertions(+), 38 deletions(-)
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
@@ -40,6 +40,19 @@ const FORCE_CITATIONS_RESET_TEXT = 2;
// this is used only for update checking
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
+
+// These must match the constants defined in zoteroIntegration.idl
+const DIALOG_ICON_STOP = 0;
+const DIALOG_ICON_WARNING = 1;
+const DIALOG_ICON_CAUTION = 2;
+
+const DIALOG_BUTTONS_OK = 0;
+const DIALOG_BUTTONS_OK_CANCEL = 1;
+const DIALOG_BUTTONS_YES_NO = 2;
+const DIALOG_BUTTONS_YES_NO_CANCEL = 3;
+
+const NOTE_FOOTNOTE = 1;
+const NOTE_ENDNOTE = 2;
Zotero.Integration = new function() {
Components.utils.import("resource://gre/modules/Services.jsm");
@@ -210,6 +223,19 @@ Zotero.Integration = new function() {
};
}
+ this.getApplication = function(agent, command, docId) {
+ // Try to load the appropriate Zotero component; otherwise display an error
+ try {
+ var componentClass = "@zotero.org/Zotero/integration/application?agent="+agent+";1";
+ Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command+(docId ? " with doc "+docId : ""));
+ return Components.classes[componentClass]
+ .getService(Components.interfaces.zoteroIntegrationApplication);
+ } catch(e) {
+ throw new Zotero.Exception.Alert("integration.error.notInstalled",
+ [], "integration.error.title");
+ }
+ },
+
/**
* Executes an integration command, first checking to make sure that versions are compatible
*/
@@ -230,17 +256,8 @@ Zotero.Integration = new function() {
inProgress = true;
// Check integration component versions
- _checkPluginVersions().then(function() {
- // Try to load the appropriate Zotero component; otherwise display an error
- try {
- var componentClass = "@zotero.org/Zotero/integration/application?agent="+agent+";1";
- Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command+(docId ? " with doc "+docId : ""));
- var application = Components.classes[componentClass]
- .getService(Components.interfaces.zoteroIntegrationApplication);
- } catch(e) {
- throw new Zotero.Exception.Alert("integration.error.notInstalled",
- [], "integration.error.title");
- }
+ return _checkPluginVersions().then(function() {
+ var application = Zotero.Integration.getApplication(agent, command, docId);
// Try to execute the command; otherwise display an error in alert service or word processor
// (depending on what is possible)
@@ -267,9 +284,7 @@ Zotero.Integration = new function() {
if(document) {
try {
document.activate();
- document.displayAlert(displayError,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ document.displayAlert(displayError, DIALOG_ICON_STOP, DIALOG_BUTTONS_OK);
} catch(e) {
showErrorInFirefox = true;
}
@@ -821,10 +836,7 @@ Zotero.Integration.CorruptFieldException.prototype = {
field = this.fieldGetter._fields[this.fieldIndex];
field.select();
this.fieldGetter._doc.activate();
- var result = this.fieldGetter._doc.displayAlert(msg,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
-
+ var result = this.fieldGetter._doc.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO_CANCEL);
if(result == 0) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
} else if(result == 1) { // No
@@ -878,9 +890,7 @@ Zotero.Integration.CorruptBibliographyException.prototype = {
var msg = Zotero.getString("integration.corruptBibliography")+'\n\n'+
Zotero.getString('integration.corruptBibliography.description');
- var result = this.fieldGetter._doc.displayAlert(msg,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
+ var result = this.fieldGetter._doc.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_OK_CANCEL);
if(result == 0) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
} else {
@@ -991,8 +1001,7 @@ Zotero.Integration.Document.prototype._getSession = Zotero.Promise.coroutine(fun
}
var warning = this._doc.displayAlert(Zotero.getString("integration.upgradeWarning"),
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
+ DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
if(!warning) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document upgrade"));
}
@@ -1007,9 +1016,11 @@ Zotero.Integration.Document.prototype._getSession = Zotero.Promise.coroutine(fun
[], "integration.error.title"));
}
- if(Zotero.Integration.sessions[data.sessionID]) {
+ if (Zotero.Integration.sessions[data.sessionID]) {
+ // If communication occured with this document since restart
this._session = Zotero.Integration.sessions[data.sessionID];
} else {
+ // Document has zotero data, but has not communicated since Zotero restart
this._session = this._createNewSession(data);
try {
yield this._session.setData(data);
@@ -1027,7 +1038,6 @@ Zotero.Integration.Document.prototype._getSession = Zotero.Promise.coroutine(fun
}
}
- this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true;
}
return Zotero.Promise.resolve(this._session);
@@ -1156,8 +1166,7 @@ Zotero.Integration.Document.prototype.removeCodes = function() {
return fieldGetter.get()
}).then(function(fields) {
var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
+ DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
if(result) {
for(var i=fields.length-1; i>=0; i--) {
fields[i].removeCode();
@@ -1301,8 +1310,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 +1616,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 +1760,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");
}
}
@@ -2351,9 +2358,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) {
@@ -3085,9 +3092,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/test/tests/integrationTests.js b/test/tests/integrationTests.js
@@ -0,0 +1,241 @@
+"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);
+ });
+ })
+ })
+});
diff --git a/test/tests/styleTest.js b/test/tests/styleTest.js
@@ -12,7 +12,7 @@ describe("Zotero.Styles", function() {
});
describe("Zotero.Styles.install", function() {
- afterEach(function* (){
+ afterEach(`${styleID} style should be installed`, function* (){
assert.isOk(Zotero.Styles.get(styleID));
yield Zotero.Styles.get(styleID).remove();
});