2015-05-05 07:17:12 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
describe("Zotero.DataObject", function() {
|
|
|
|
var types = ['collection', 'item', 'search'];
|
|
|
|
|
2015-07-31 08:03:09 +00:00
|
|
|
describe("#key", function () {
|
|
|
|
it("shouldn't update .loaded on get if unset", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
if (type == 'item') {
|
|
|
|
var param = 'book';
|
|
|
|
}
|
|
|
|
let obj = new Zotero[Zotero.Utilities.capitalize(type)](param);
|
|
|
|
obj.libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
assert.isNull(obj.key);
|
|
|
|
assert.isFalse(obj._loaded.primaryData);
|
|
|
|
obj.key = Zotero.DataObjectUtilities.generateKey();
|
|
|
|
}
|
2015-05-05 18:39:24 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-05-20 23:28:35 +00:00
|
|
|
describe("#version", function () {
|
|
|
|
it("should be set to 0 after creating object", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = yield createDataObject(type);
|
|
|
|
assert.equal(obj.version, 0);
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should be set after creating object", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = yield createDataObject(type, { version: 1234 });
|
|
|
|
assert.equal(obj.version, 1234, type + " version mismatch");
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-05-13 23:30:22 +00:00
|
|
|
describe("#synced", function () {
|
2015-07-19 21:17:32 +00:00
|
|
|
it("should be set to false after creating object", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
assert.isFalse(obj.synced);
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
2015-05-13 23:30:22 +00:00
|
|
|
});
|
|
|
|
|
2015-07-19 21:17:32 +00:00
|
|
|
it("should be set to false after modifying object", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
|
|
|
|
obj.synced = true;
|
|
|
|
yield obj.saveTx();
|
|
|
|
|
|
|
|
if (type == 'item') {
|
|
|
|
yield obj.loadItemData();
|
|
|
|
obj.setField('title', Zotero.Utilities.randomString());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
obj.name = Zotero.Utilities.randomString();
|
|
|
|
}
|
|
|
|
yield obj.saveTx();
|
|
|
|
assert.isFalse(obj.synced);
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
2015-05-13 23:30:22 +00:00
|
|
|
});
|
|
|
|
|
2015-07-19 21:17:32 +00:00
|
|
|
it("should be changed to true explicitly with no other changes", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
|
|
|
|
obj.synced = true;
|
|
|
|
yield obj.saveTx();
|
|
|
|
assert.isTrue(obj.synced);
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should be changed to true explicitly with other field changes", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
|
|
|
|
if (type == 'item') {
|
|
|
|
obj.setField('title', Zotero.Utilities.randomString());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
obj.name = Zotero.Utilities.randomString();
|
|
|
|
}
|
|
|
|
obj.synced = true;
|
|
|
|
yield obj.saveTx();
|
|
|
|
assert.isTrue(obj.synced);
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should remain at true if set explicitly", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
obj.synced = true;
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
assert.isTrue(obj.synced);
|
|
|
|
|
|
|
|
if (type == 'item') {
|
|
|
|
obj.setField('title', 'test');
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
obj.name = Zotero.Utilities.randomString();
|
|
|
|
}
|
|
|
|
obj.synced = true;
|
|
|
|
yield obj.saveTx();
|
|
|
|
assert.isTrue(obj.synced);
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
2015-05-13 23:30:22 +00:00
|
|
|
});
|
|
|
|
|
2015-07-19 21:17:32 +00:00
|
|
|
it("shouldn't cause a save if unchanged and nothing else changed", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
obj.synced = true;
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
assert.isTrue(obj.synced);
|
|
|
|
|
|
|
|
obj.synced = true;
|
|
|
|
assert.isFalse(obj.hasChanged(), type + " shouldn't be changed");
|
|
|
|
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
obj.synced = false;
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
assert.isFalse(obj.synced);
|
|
|
|
obj.synced = false;
|
|
|
|
assert.isFalse(obj.hasChanged(), type + " shouldn't be changed");
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2015-05-13 23:30:22 +00:00
|
|
|
it("should be unchanged if skipSyncedUpdate passed", function* () {
|
2015-07-19 21:17:32 +00:00
|
|
|
for (let type of types) {
|
|
|
|
var obj = createUnsavedDataObject(type);
|
|
|
|
var id = yield obj.saveTx();
|
|
|
|
|
|
|
|
obj.synced = 1;
|
|
|
|
yield obj.saveTx();
|
|
|
|
|
|
|
|
if (type == 'item') {
|
|
|
|
yield obj.loadItemData();
|
|
|
|
obj.setField('title', Zotero.Utilities.randomString());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
obj.name = Zotero.Utilities.randomString();
|
|
|
|
}
|
|
|
|
yield obj.saveTx({
|
|
|
|
skipSyncedUpdate: true
|
|
|
|
});
|
|
|
|
assert.ok(obj.synced);
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
2015-05-13 23:30:22 +00:00
|
|
|
});
|
|
|
|
})
|
|
|
|
|
2015-07-31 08:03:09 +00:00
|
|
|
describe("#loadPrimaryData()", function () {
|
|
|
|
it("should load unloaded primary data if partially set", function* () {
|
|
|
|
var objs = {};
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = createUnsavedDataObject(type);
|
|
|
|
yield obj.save({
|
|
|
|
skipCache: true
|
|
|
|
});
|
|
|
|
objs[type] = {
|
|
|
|
key: obj.key,
|
|
|
|
version: obj.version
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = new Zotero[Zotero.Utilities.capitalize(type)];
|
|
|
|
obj.libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
obj.key = objs[type].key;
|
|
|
|
yield obj.loadPrimaryData();
|
|
|
|
assert.equal(obj.version, objs[type].version);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#loadAllData()", function () {
|
|
|
|
it("should load data on a regular item", function* () {
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
yield item.loadAllData();
|
|
|
|
assert.throws(item.getNote.bind(item), 'getNote() can only be called on notes and attachments');
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should load data on an attachment item", function* () {
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
yield item.loadAllData();
|
|
|
|
assert.equal(item.getNote(), '');
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should load data on a note item", function* () {
|
|
|
|
var item = new Zotero.Item('note');
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
yield item.loadAllData();
|
|
|
|
assert.equal(item.getNote(), '');
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-05-05 07:17:12 +00:00
|
|
|
describe("#save()", function () {
|
|
|
|
it("should add new identifiers to cache", function* () {
|
|
|
|
// Collection
|
|
|
|
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('collection');
|
|
|
|
var obj = new Zotero.Collection;
|
|
|
|
obj.name = "Test";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield obj.saveTx();
|
2015-05-05 07:17:12 +00:00
|
|
|
var { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
|
|
|
|
assert.typeOf(key, 'string');
|
|
|
|
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
|
|
|
|
|
|
|
|
// Search
|
|
|
|
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('search');
|
|
|
|
var obj = new Zotero.Search;
|
|
|
|
obj.name = "Test";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield obj.saveTx();
|
2015-05-05 07:17:12 +00:00
|
|
|
var { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
|
|
|
|
assert.typeOf(key, 'string');
|
|
|
|
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
|
|
|
|
|
|
|
|
// Item
|
|
|
|
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('item');
|
|
|
|
var obj = new Zotero.Item('book');
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield obj.saveTx();
|
2015-05-05 07:17:12 +00:00
|
|
|
var { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
|
|
|
|
assert.typeOf(key, 'string');
|
|
|
|
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
|
|
|
|
})
|
|
|
|
})
|
2015-05-22 01:56:04 +00:00
|
|
|
|
2015-06-02 07:28:46 +00:00
|
|
|
describe("#erase()", function () {
|
|
|
|
it("shouldn't trigger notifier if skipNotifier is passed", function* () {
|
|
|
|
let observerIDs = [];
|
|
|
|
let promises = [];
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = yield createDataObject(type);
|
|
|
|
// For items, test automatic child item deletion
|
|
|
|
if (type == 'item') {
|
|
|
|
yield createDataObject(type, { itemType: 'note', parentID: obj.id });
|
|
|
|
}
|
|
|
|
|
|
|
|
let deferred = Zotero.Promise.defer();
|
|
|
|
promises.push(deferred.promise);
|
|
|
|
observerIDs.push(Zotero.Notifier.registerObserver(
|
|
|
|
{
|
|
|
|
notify: function (event) {
|
|
|
|
if (event == 'delete') {
|
|
|
|
deferred.reject("Notifier called for erase on " + type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
type,
|
|
|
|
'test'
|
|
|
|
));
|
|
|
|
yield obj.eraseTx({
|
|
|
|
skipNotifier: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
yield Zotero.Promise.all(promises)
|
|
|
|
// Give notifier time to trigger
|
|
|
|
.timeout(100).catch(Zotero.Promise.TimeoutError, (e) => {})
|
|
|
|
|
|
|
|
for (let id of observerIDs) {
|
|
|
|
Zotero.Notifier.unregisterObserver(id);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-05-22 01:56:04 +00:00
|
|
|
describe("#updateVersion()", function() {
|
|
|
|
it("should update the object version", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = yield createDataObject(type);
|
|
|
|
assert.equal(obj.version, 0);
|
|
|
|
|
|
|
|
yield obj.updateVersion(1234);
|
|
|
|
assert.equal(obj.version, 1234);
|
|
|
|
assert.isFalse(obj.hasChanged());
|
|
|
|
|
|
|
|
obj.synced = true;
|
|
|
|
assert.ok(obj.hasChanged());
|
|
|
|
yield obj.updateVersion(1235);
|
|
|
|
assert.equal(obj.version, 1235);
|
|
|
|
assert.ok(obj.hasChanged());
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#updateSynced()", function() {
|
|
|
|
it("should update the object sync status", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let obj = yield createDataObject(type);
|
|
|
|
assert.isFalse(obj.synced);
|
|
|
|
|
|
|
|
yield obj.updateSynced(false);
|
|
|
|
assert.isFalse(obj.synced);
|
|
|
|
assert.isFalse(obj.hasChanged());
|
|
|
|
|
|
|
|
yield obj.updateSynced(true);
|
|
|
|
assert.ok(obj.synced);
|
|
|
|
assert.isFalse(obj.hasChanged());
|
|
|
|
|
|
|
|
obj.version = 1234;
|
|
|
|
assert.ok(obj.hasChanged());
|
|
|
|
yield obj.updateSynced(false);
|
|
|
|
assert.isFalse(obj.synced);
|
|
|
|
assert.ok(obj.hasChanged());
|
|
|
|
|
|
|
|
yield obj.eraseTx();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
Relations overhaul (requires new DB upgrade from 4.0)
Relations are now properties of collections and items rather than
first-class objects, stored in separate collectionRelations and
itemRelations tables with ids for subjects, with foreign keys to the
associated data objects.
Related items now use dc:relation relations rather than a separate table
(among other reasons, because API syncing won't necessarily sync both
items at the same time, so they can't be stored by id).
The UI assigns related-item relations bidirectionally, and checks for
related-item and linked-object relations are done unidirectionally by
default.
dc:isReplacedBy is now dc:replaces, so that the subject is an existing
object, and the predicate is now named
Zotero.Attachments.replacedItemPredicate.
Some additional work is still needed, notably around following
replaced-item relations, and migration needs to be tested more fully,
but this seems to mostly work.
2015-06-02 00:09:39 +00:00
|
|
|
|
|
|
|
describe("Relations", function () {
|
|
|
|
var types = ['collection', 'item'];
|
|
|
|
|
|
|
|
function makeObjectURI(objectType) {
|
|
|
|
var objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
|
|
|
|
return 'http://zotero.org/groups/1/' + objectTypePlural + '/'
|
|
|
|
+ Zotero.Utilities.generateObjectKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
describe("#addRelation()", function () {
|
|
|
|
it("should add a relation to an object", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let predicate = 'owl:sameAs';
|
|
|
|
let object = makeObjectURI(type);
|
|
|
|
let obj = createUnsavedDataObject(type);
|
|
|
|
obj.addRelation(predicate, object);
|
|
|
|
yield obj.saveTx();
|
|
|
|
var relations = obj.getRelations();
|
|
|
|
assert.property(relations, predicate);
|
|
|
|
assert.include(relations[predicate], object);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#removeRelation()", function () {
|
|
|
|
it("should remove a relation from an object", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let predicate = 'owl:sameAs';
|
|
|
|
let object = makeObjectURI(type);
|
|
|
|
let obj = createUnsavedDataObject(type);
|
|
|
|
obj.addRelation(predicate, object);
|
|
|
|
yield obj.saveTx();
|
|
|
|
|
|
|
|
obj.removeRelation(predicate, object);
|
|
|
|
yield obj.saveTx();
|
|
|
|
|
|
|
|
assert.lengthOf(Object.keys(obj.getRelations()), 0);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#hasRelation()", function () {
|
|
|
|
it("should return true if an object has a given relation", function* () {
|
|
|
|
for (let type of types) {
|
|
|
|
let predicate = 'owl:sameAs';
|
|
|
|
let object = makeObjectURI(type);
|
|
|
|
let obj = createUnsavedDataObject(type);
|
|
|
|
obj.addRelation(predicate, object);
|
|
|
|
yield obj.saveTx();
|
|
|
|
assert.ok(obj.hasRelation(predicate, object));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#_getLinkedObject()", function () {
|
|
|
|
it("should return a linked object in another library", function* () {
|
|
|
|
var group = yield getGroup();
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
var item2 = yield createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
var item2URI = Zotero.URI.getItemURI(item2);
|
|
|
|
|
|
|
|
yield item2.addLinkedItem(item1);
|
|
|
|
var linkedItem = yield item1.getLinkedItem(item2.libraryID);
|
|
|
|
assert.equal(linkedItem.id, item2.id);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("shouldn't return reverse linked objects by default", function* () {
|
|
|
|
var group = yield getGroup();
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
var item1URI = Zotero.URI.getItemURI(item1);
|
|
|
|
var item2 = yield createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
|
|
|
|
yield item2.addLinkedItem(item1);
|
|
|
|
var linkedItem = yield item2.getLinkedItem(item1.libraryID);
|
|
|
|
assert.isFalse(linkedItem);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should return reverse linked objects with bidirectional flag", function* () {
|
|
|
|
var group = yield getGroup();
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
var item1URI = Zotero.URI.getItemURI(item1);
|
|
|
|
var item2 = yield createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
|
|
|
|
yield item2.addLinkedItem(item1);
|
|
|
|
var linkedItem = yield item2.getLinkedItem(item1.libraryID, true);
|
|
|
|
assert.equal(linkedItem.id, item1.id);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#_addLinkedObject()", function () {
|
|
|
|
it("should add an owl:sameAs relation", function* () {
|
|
|
|
var group = yield getGroup();
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
var dateModified = item1.getField('dateModified');
|
|
|
|
var item2 = yield createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
var item2URI = Zotero.URI.getItemURI(item2);
|
|
|
|
|
|
|
|
yield item2.addLinkedItem(item1);
|
|
|
|
var preds = item1.getRelationsByPredicate(Zotero.Relations.linkedObjectPredicate);
|
|
|
|
assert.include(preds, item2URI);
|
|
|
|
|
|
|
|
// Make sure Date Modified hasn't changed
|
|
|
|
assert.equal(item1.getField('dateModified'), dateModified);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2015-05-05 07:17:12 +00:00
|
|
|
})
|