Merge branch '4.0' into sjk/659
This commit is contained in:
commit
9bb01d737c
21 changed files with 10319 additions and 158 deletions
|
@ -1,3 +1,8 @@
|
|||
// Useful "constants"
|
||||
var sqlDateTimeRe = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||
var isoDateTimeRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
||||
var zoteroObjectKeyRe = /^[23456789ABCDEFGHIJKLMNPQRSTUVWXYZ]{8}$/; // based on Zotero.Utilities::generateObjectKey()
|
||||
|
||||
/**
|
||||
* Waits for a DOM event on the specified node. Returns a promise
|
||||
* resolved with the event.
|
||||
|
@ -217,8 +222,8 @@ function uninstallPDFTools() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a promise for the nsIFile corresponding to the test data
|
||||
* directory (i.e., test/tests/data)
|
||||
* Returns the nsIFile corresponding to the test data directory
|
||||
* (i.e., test/tests/data)
|
||||
*/
|
||||
function getTestDataDirectory() {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
@ -229,6 +234,28 @@ function getTestDataDirectory() {
|
|||
QueryInterface(Components.interfaces.nsIFileURL).file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an absolute path to an empty temporary directory
|
||||
* (i.e., test/tests/data)
|
||||
*/
|
||||
var getTempDirectory = Zotero.Promise.coroutine(function* getTempDirectory() {
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
let path,
|
||||
attempts = 3,
|
||||
zoteroTmpDirPath = Zotero.getTempDirectory().path;
|
||||
while (attempts--) {
|
||||
path = OS.Path.join(zoteroTmpDirPath, Zotero.Utilities.randomString());
|
||||
try {
|
||||
yield OS.File.makeDir(path, { ignoreExisting: false });
|
||||
break;
|
||||
} catch (e) {
|
||||
if (!attempts) throw e; // Throw on last attempt
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
});
|
||||
|
||||
/**
|
||||
* Resets the Zotero DB and restarts Zotero. Returns a promise resolved
|
||||
* when this finishes.
|
||||
|
@ -242,6 +269,253 @@ function resetDB() {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to JSON.stringify, except that object properties are stringified
|
||||
* in a sorted order.
|
||||
*/
|
||||
function stableStringify(obj) {
|
||||
return JSON.stringify(obj, function(k, v) {
|
||||
if (v && typeof v == "object" && !Array.isArray(v)) {
|
||||
let o = {},
|
||||
keys = Object.keys(v).sort();
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
o[keys[i]] = v[keys[i]];
|
||||
}
|
||||
return o;
|
||||
}
|
||||
return v;
|
||||
}, "\t");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads specified sample data from file
|
||||
*/
|
||||
function loadSampleData(dataName) {
|
||||
let data = Zotero.File.getContentsFromURL('resource://zotero-unit-tests/data/' + dataName + '.js');
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates sample item data that is stored in data/sampleItemData.js
|
||||
*/
|
||||
function generateAllTypesAndFieldsData() {
|
||||
let data = {};
|
||||
let itemTypes = Zotero.ItemTypes.getTypes();
|
||||
// For most fields, use the field name as the value, but this doesn't
|
||||
// work well for some fields that expect values in certain formats
|
||||
let specialValues = {
|
||||
date: '1999-12-31',
|
||||
filingDate: '2000-01-02',
|
||||
accessDate: '1997-06-13 23:59:58',
|
||||
number: 3,
|
||||
numPages: 4,
|
||||
issue: 5,
|
||||
volume: 6,
|
||||
numberOfVolumes: 7,
|
||||
edition: 8,
|
||||
seriesNumber: 9,
|
||||
ISBN: '978-1-234-56789-7',
|
||||
ISSN: '1234-5679',
|
||||
url: 'http://www.example.com',
|
||||
pages: '1-10',
|
||||
DOI: '10.1234/example.doi',
|
||||
runningTime: '1:22:33',
|
||||
language: 'en-US'
|
||||
};
|
||||
|
||||
// Item types that should not be included in sample data
|
||||
let excludeItemTypes = ['note', 'attachment'];
|
||||
|
||||
for (let i = 0; i < itemTypes.length; i++) {
|
||||
if (excludeItemTypes.indexOf(itemTypes[i].name) != -1) continue;
|
||||
|
||||
let itemFields = data[itemTypes[i].name] = {
|
||||
itemType: itemTypes[i].name
|
||||
};
|
||||
|
||||
let fields = Zotero.ItemFields.getItemTypeFields(itemTypes[i].id);
|
||||
for (let j = 0; j < fields.length; j++) {
|
||||
let field = fields[j];
|
||||
field = Zotero.ItemFields.getBaseIDFromTypeAndField(itemTypes[i].id, field) || field;
|
||||
|
||||
let name = Zotero.ItemFields.getName(field),
|
||||
value;
|
||||
|
||||
// Use field name as field value
|
||||
if (specialValues[name]) {
|
||||
value = specialValues[name];
|
||||
} else {
|
||||
value = name.charAt(0).toUpperCase() + name.substr(1);
|
||||
// Make it look nice (sentence case)
|
||||
value = value.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.replace(/ [A-Z](?![A-Z])/g, m => m.toLowerCase()); // not all-caps words
|
||||
}
|
||||
|
||||
itemFields[name] = value;
|
||||
}
|
||||
|
||||
let creatorTypes = Zotero.CreatorTypes.getTypesForItemType(itemTypes[i].id),
|
||||
creators = itemFields.creators = [];
|
||||
for (let j = 0; j < creatorTypes.length; j++) {
|
||||
let typeName = creatorTypes[j].name;
|
||||
creators.push({
|
||||
creatorType: typeName,
|
||||
firstName: typeName + 'First',
|
||||
lastName: typeName + 'Last'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the database with sample items
|
||||
* The field values should be in the form exactly as they would appear in Zotero
|
||||
*/
|
||||
function populateDBWithSampleData(data) {
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
for (let itemName in data) {
|
||||
let item = data[itemName];
|
||||
let zItem = new Zotero.Item(item.itemType);
|
||||
for (let itemField in item) {
|
||||
if (itemField == 'itemType') continue;
|
||||
|
||||
if (itemField == 'creators') {
|
||||
zItem.setCreators(item[itemField]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemField == 'tags') {
|
||||
// Must save item first
|
||||
continue;
|
||||
}
|
||||
|
||||
zItem.setField(itemField, item[itemField]);
|
||||
}
|
||||
|
||||
if (item.tags && item.tags.length) {
|
||||
for (let i=0; i<item.tags.length; i++) {
|
||||
zItem.addTag(item.tags[i].tag, item.tags[i].type);
|
||||
}
|
||||
}
|
||||
|
||||
item.id = yield zItem.save();
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
var generateItemJSONData = Zotero.Promise.coroutine(function* generateItemJSONData(options, currentData) {
|
||||
let items = yield populateDBWithSampleData(loadSampleData('allTypesAndFields')),
|
||||
jsonData = {};
|
||||
|
||||
for (let itemName in items) {
|
||||
let zItem = yield Zotero.Items.getAsync(items[itemName].id);
|
||||
jsonData[itemName] = yield zItem.toJSON(options);
|
||||
|
||||
// Don't replace some fields that _always_ change (e.g. item keys)
|
||||
// as long as it follows expected format
|
||||
// This makes it easier to generate more meaningful diffs
|
||||
if (!currentData || !currentData[itemName]) continue;
|
||||
|
||||
for (let field in jsonData[itemName]) {
|
||||
let oldVal = currentData[itemName][field];
|
||||
if (!oldVal) continue;
|
||||
|
||||
let val = jsonData[itemName][field];
|
||||
switch (field) {
|
||||
case 'dateAdded':
|
||||
case 'dateModified':
|
||||
if (!isoDateTimeRe.test(oldVal) || !isoDateTimeRe.test(val)) continue;
|
||||
break;
|
||||
case 'key':
|
||||
if (!zoteroObjectKeyRe.test(oldVal) || !zoteroObjectKeyRe.test(val)) continue;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
jsonData[itemName][field] = oldVal;
|
||||
}
|
||||
}
|
||||
|
||||
return jsonData;
|
||||
});
|
||||
|
||||
var generateCiteProcJSExportData = Zotero.Promise.coroutine(function* generateCiteProcJSExportData(currentData) {
|
||||
let items = yield populateDBWithSampleData(loadSampleData('allTypesAndFields')),
|
||||
cslExportData = {};
|
||||
|
||||
for (let itemName in items) {
|
||||
let zItem = yield Zotero.Items.getAsync(items[itemName].id);
|
||||
cslExportData[itemName] = Zotero.Cite.System.prototype.retrieveItem(zItem);
|
||||
|
||||
if (!currentData || !currentData[itemName]) continue;
|
||||
|
||||
// Don't replace id as long as it follows expected format
|
||||
if (Number.isInteger(currentData[itemName].id)
|
||||
&& Number.isInteger(cslExportData[itemName].id)
|
||||
) {
|
||||
cslExportData[itemName].id = currentData[itemName].id;
|
||||
}
|
||||
}
|
||||
|
||||
return cslExportData;
|
||||
});
|
||||
|
||||
var generateTranslatorExportData = Zotero.Promise.coroutine(function* generateTranslatorExportData(legacy, currentData) {
|
||||
let items = yield populateDBWithSampleData(loadSampleData('allTypesAndFields')),
|
||||
translatorExportData = {};
|
||||
|
||||
let itemGetter = new Zotero.Translate.ItemGetter();
|
||||
itemGetter.legacy = !!legacy;
|
||||
|
||||
for (let itemName in items) {
|
||||
let zItem = yield Zotero.Items.getAsync(items[itemName].id);
|
||||
itemGetter._itemsLeft = [zItem];
|
||||
translatorExportData[itemName] = yield itemGetter.nextItem();
|
||||
|
||||
// Don't replace some fields that _always_ change (e.g. item keys)
|
||||
if (!currentData || !currentData[itemName]) continue;
|
||||
|
||||
// For simplicity, be more lenient than for item key
|
||||
let uriRe = /^http:\/\/zotero\.org\/users\/local\/\w{8}\/items\/\w{8}$/;
|
||||
let itemIDRe = /^\d+$/;
|
||||
for (let field in translatorExportData[itemName]) {
|
||||
let oldVal = currentData[itemName][field];
|
||||
if (!oldVal) continue;
|
||||
|
||||
let val = translatorExportData[itemName][field];
|
||||
switch (field) {
|
||||
case 'uri':
|
||||
if (!uriRe.test(oldVal) || !uriRe.test(val)) continue;
|
||||
break;
|
||||
case 'itemID':
|
||||
if (!itemIDRe.test(oldVal) || !itemIDRe.test(val)) continue;
|
||||
break;
|
||||
case 'key':
|
||||
if (!zoteroObjectKeyRe.test(oldVal) || !zoteroObjectKeyRe.test(val)) continue;
|
||||
break;
|
||||
case 'dateAdded':
|
||||
case 'dateModified':
|
||||
if (legacy) {
|
||||
if (!sqlDateTimeRe.test(oldVal) || !sqlDateTimeRe.test(val)) continue;
|
||||
} else {
|
||||
if (!isoDateTimeRe.test(oldVal) || !isoDateTimeRe.test(val)) continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
translatorExportData[itemName][field] = oldVal;
|
||||
}
|
||||
}
|
||||
|
||||
return translatorExportData;
|
||||
});
|
||||
|
||||
/**
|
||||
* Imports an attachment from a test file.
|
||||
|
@ -253,4 +527,3 @@ function importFileAttachment(filename) {
|
|||
filename.split('/').forEach((part) => testfile.append(part));
|
||||
return Zotero.Attachments.importFromFile({file: testfile});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue