Merge pull request #1202 from adomasven/fix/doc-pref-window-unloaded-styles

Fix document preferences dialog failing when styles unloaded.
This commit is contained in:
Adomas Ven 2017-04-12 11:52:58 +03:00 committed by GitHub
commit 2a5dbaa204
8 changed files with 620 additions and 107 deletions

View File

@ -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<nStyles; i++) {
for (let i=0; i < styles.length; i++) {
var itemNode = document.createElement("listitem");
itemNode.setAttribute("value", styles[i].styleID);
itemNode.setAttribute("label", styles[i].title);
listbox.appendChild(itemNode);
if(styles[i].styleID == _io.style) {
selectIndex = index;
selectIndex = i;
}
index++;
}
let requestedLocale;
@ -171,7 +172,7 @@ var Zotero_File_Interface_Bibliography = new function() {
// set style to false, in case this is cancelled
_io.style = false;
};
});
/*
* Called when locale is changed

View File

@ -335,8 +335,8 @@ Zotero.File = new function(){
/*
* Return a promise for the contents of a URL as a string
*/
this.getContentsFromURLAsync = function (url) {
return Zotero.HTTP.request("GET", url, { responseType: "text" })
this.getContentsFromURLAsync = function (url, options={}) {
return Zotero.HTTP.request("GET", url, Object.assign(options, { responseType: "text" }))
.then(function (xmlhttp) {
return xmlhttp.response;
});

View File

@ -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 {
@ -928,7 +938,7 @@ Zotero.Integration.Document.prototype._createNewSession = function _createNewSes
* dontRunSetDocPrefs is true and no session was found, or rejected with
* Zotero.Exception.UserCancelled if the document preferences window was cancelled.
*/
Zotero.Integration.Document.prototype._getSession = function _getSession(require, dontRunSetDocPrefs) {
Zotero.Integration.Document.prototype._getSession = Zotero.Promise.coroutine(function *(require, dontRunSetDocPrefs) {
var dataString = this._doc.getDocumentData(),
data,
me = this;
@ -967,7 +977,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
// Set doc prefs if no data string yet
this._session = this._createNewSession(data);
this._session.setData(data);
yield this._session.setData(data);
if(dontRunSetDocPrefs) return Zotero.Promise.resolve(false);
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
@ -991,8 +1001,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
}
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,15 +1016,27 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
[], "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 {
this._session.setData(data);
yield this._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) {
let displayError = Zotero.getString("integration.error.styleMissing", data.style.styleID);
if (/^https?:\/\/(www\.)?(zotero\.org|citationstyles\.org)/.test(data.style.styleID) ||
me._doc.displayAlert(displayError, DIALOG_ICON_WARNING, DIALOG_BUTTONS_YES_NO)) {
yield Zotero.Styles.install({url: data.style.styleID}, data.style.styleID, true);
yield this._session.setData(data, true);
return Zotero.Promise.resolve(this._session);
}
}
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
this._app.secondaryFieldType).then(function(status) {
me._doc.setDocumentData(me._session.data.serializeXML());
@ -1027,12 +1048,11 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true;
}
return Zotero.Promise.resolve(this._session);
}
};
});
/**
* Adds a citation to the current document.
@ -1156,8 +1176,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 +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;

View File

@ -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

View File

@ -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?

116
test/tests/data/cell.csl Normal file
View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" version="1.0" class="in-text" default-locale="en-US" demote-non-dropping-particle="sort-only" page-range-format="expanded">
<info>
<title>Cell</title>
<id>http://www.zotero.org/styles/cell</id>
<link href="http://www.zotero.org/styles/cell" rel="self"/>
<link href="http://www.cell.com/authors" rel="documentation"/>
<author>
<name>Adam Mark</name>
<email>a.mark@uoguelph.ca</email>
</author>
<contributor>
<name>Julian Onions</name>
<email>julian.onions@gmail.com</email>
</contributor>
<contributor>
<name>Aurimas Vinckevicius</name>
<email>aurimas.dev@gmail.com</email>
</contributor>
<category citation-format="author-date"/>
<category field="biology"/>
<issn>0092-8674</issn>
<eissn>1097-4172</eissn>
<summary>The Cell journal style. Original by Julian Onions.</summary>
<updated>2014-09-06T22:02:33+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
</info>
<macro name="author-short">
<names variable="author">
<name form="short" and="text"/>
</names>
</macro>
<macro name="author-count">
<names variable="author">
<name form="count"/>
</names>
</macro>
<macro name="author">
<names variable="author">
<name name-as-sort-order="all" initialize-with="." and="text" delimiter-precedes-last="always"/>
</names>
</macro>
<macro name="issued">
<date variable="issued">
<date-part name="year"/>
</date>
</macro>
<macro name="publisher">
<group prefix="(" delimiter=": " suffix=")">
<text variable="publisher-place"/>
<text variable="publisher"/>
</group>
</macro>
<macro name="editor">
<names variable="editor">
<name initialize-with="." and="text" delimiter-precedes-last="always"/>
<label form="short" prefix=", "/>
</names>
</macro>
<citation et-al-min="3" et-al-use-first="1" disambiguate-add-year-suffix="true" collapse="year">
<sort>
<key macro="author-short" names-min="1" names-use-first="1"/>
<key macro="author-count" names-min="3" names-use-first="3"/>
<key macro="author" names-min="3" names-use-first="1"/>
<key macro="issued"/>
<key variable="title"/>
</sort>
<layout prefix="(" suffix=")" delimiter="; ">
<group delimiter=", ">
<text macro="author-short"/>
<text macro="issued"/>
</group>
</layout>
</citation>
<bibliography et-al-min="11" et-al-use-first="10">
<sort>
<key macro="author-short" names-min="1" names-use-first="1"/>
<key macro="author-count" names-min="3" names-use-first="3"/>
<key macro="author" names-min="3" names-use-first="1"/>
<key macro="issued"/>
</sort>
<layout suffix=".">
<group delimiter=" ">
<text macro="author"/>
<text macro="issued" prefix="(" suffix=")."/>
<choose>
<if type="article article-magazine article-newspaper article-journal review" match="any">
<text variable="title" suffix="."/>
<text variable="container-title" form="short" text-case="title"/>
<group delimiter=", ">
<text variable="volume" font-style="italic"/>
<text variable="page"/>
</group>
</if>
<else-if type="chapter paper-conference" match="any">
<text variable="title" suffix="."/>
<text variable="container-title" prefix="In " suffix="," text-case="title"/>
<text macro="editor"/>
<text macro="publisher" suffix=","/>
<label variable="page" form="short"/>
<text variable="page"/>
</else-if>
<else-if type="thesis">
<text variable="title" suffix="."/>
<text variable="genre" suffix="."/>
<text variable="publisher"/>
</else-if>
<else>
<text variable="title"/>
<text macro="publisher"/>
</else>
</choose>
</group>
</layout>
</bibliography>
</style>

View File

@ -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();
});
});
});
});
});

45
test/tests/styleTest.js Normal file
View File

@ -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);
})
});
});