2015-05-04 09:59:40 +00:00
|
|
|
"use strict";
|
|
|
|
|
2015-04-26 06:44:29 +00:00
|
|
|
describe("Zotero.Collection", function() {
|
|
|
|
describe("#save()", function () {
|
|
|
|
it("should save a new collection", function* () {
|
|
|
|
var name = "Test";
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = name;
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx();
|
2015-05-21 07:19:28 +00:00
|
|
|
assert.equal(collection.name, name);
|
2015-04-26 06:44:29 +00:00
|
|
|
collection = yield Zotero.Collections.getAsync(id);
|
|
|
|
assert.equal(collection.name, name);
|
|
|
|
});
|
|
|
|
})
|
2015-05-04 09:59:40 +00:00
|
|
|
|
2016-01-17 21:55:34 +00:00
|
|
|
describe("#erase()", function () {
|
|
|
|
it("should delete a collection but not its descendant item by default", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [collection.id] });
|
|
|
|
assert.isTrue(collection.hasItem(item.id));
|
|
|
|
|
|
|
|
yield collection.eraseTx();
|
|
|
|
|
|
|
|
assert.isFalse((yield Zotero.Items.getAsync(item.id)).deleted);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should delete a collection and trash its descendant items with deleteItems: true", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item1 = yield createDataObject('item', { collections: [collection.id] });
|
|
|
|
var item2 = yield createDataObject('item', { collections: [collection.id] });
|
|
|
|
assert.isTrue(collection.hasItem(item1.id));
|
|
|
|
assert.isTrue(collection.hasItem(item2.id));
|
|
|
|
|
|
|
|
yield collection.eraseTx({ deleteItems: true });
|
|
|
|
|
|
|
|
assert.isTrue((yield Zotero.Items.getAsync(item1.id)).deleted);
|
|
|
|
assert.isTrue((yield Zotero.Items.getAsync(item2.id)).deleted);
|
2017-02-21 05:38:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("should clear collection from item cache", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [collection.id] });
|
|
|
|
assert.lengthOf(item.getCollections(), 1);
|
|
|
|
yield collection.eraseTx();
|
|
|
|
assert.lengthOf(item.getCollections(), 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should clear subcollection from descendent item cache", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var subcollection = yield createDataObject('collection', { parentID: collection.id });
|
|
|
|
var item = yield createDataObject('item', { collections: [subcollection.id] });
|
|
|
|
assert.lengthOf(item.getCollections(), 1);
|
|
|
|
yield collection.eraseTx();
|
|
|
|
assert.lengthOf(item.getCollections(), 0);
|
|
|
|
});
|
2017-09-13 05:01:36 +00:00
|
|
|
|
|
|
|
it("should clear collection from item cache in deleteItems mode", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [collection.id] });
|
|
|
|
assert.lengthOf(item.getCollections(), 1);
|
|
|
|
yield collection.eraseTx({ deleteItems: true });
|
|
|
|
assert.lengthOf(item.getCollections(), 0);
|
|
|
|
});
|
2021-05-06 07:29:50 +00:00
|
|
|
|
|
|
|
it("should apply 'skipDeleteLog: true' to subcollections", async function () {
|
|
|
|
var collection1 = await createDataObject('collection');
|
|
|
|
var collection2 = await createDataObject('collection', { parentID: collection1.id });
|
|
|
|
var collection3 = await createDataObject('collection', { parentID: collection2.id });
|
|
|
|
|
|
|
|
await collection1.eraseTx({ skipDeleteLog: true });
|
|
|
|
|
|
|
|
var deleted = await Zotero.Sync.Data.Local.getDeleted('collection', collection1.libraryID);
|
|
|
|
|
|
|
|
// No collections should be in the delete log
|
|
|
|
assert.notInclude(deleted, collection1.key);
|
|
|
|
assert.notInclude(deleted, collection2.key);
|
|
|
|
assert.notInclude(deleted, collection3.key);
|
|
|
|
});
|
2016-01-17 21:55:34 +00:00
|
|
|
})
|
|
|
|
|
2015-05-04 09:59:40 +00:00
|
|
|
describe("#version", function () {
|
|
|
|
it("should set object version", function* () {
|
|
|
|
var version = 100;
|
|
|
|
var collection = new Zotero.Collection
|
|
|
|
collection.version = version;
|
|
|
|
collection.name = "Test";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx();
|
2015-05-21 07:19:28 +00:00
|
|
|
assert.equal(collection.version, version);
|
2015-05-04 09:59:40 +00:00
|
|
|
collection = yield Zotero.Collections.getAsync(id);
|
|
|
|
assert.equal(collection.version, version);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
|
2015-05-05 18:08:28 +00:00
|
|
|
describe("#parentKey", function () {
|
2015-05-04 09:59:40 +00:00
|
|
|
it("should set parent collection for new collections", function* () {
|
|
|
|
var parentCol = new Zotero.Collection
|
|
|
|
parentCol.name = "Parent";
|
2015-05-10 08:20:47 +00:00
|
|
|
var parentID = yield parentCol.saveTx();
|
2015-05-04 09:59:40 +00:00
|
|
|
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
|
|
|
|
|
|
|
|
var col = new Zotero.Collection
|
|
|
|
col.name = "Child";
|
|
|
|
col.parentKey = parentKey;
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield col.saveTx();
|
2015-05-21 07:19:28 +00:00
|
|
|
assert.equal(col.parentKey, parentKey);
|
2015-05-04 09:59:40 +00:00
|
|
|
col = yield Zotero.Collections.getAsync(id);
|
|
|
|
assert.equal(col.parentKey, parentKey);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should change parent collection for existing collections", function* () {
|
|
|
|
// Create initial parent collection
|
|
|
|
var parentCol = new Zotero.Collection
|
|
|
|
parentCol.name = "Parent";
|
2015-05-10 08:20:47 +00:00
|
|
|
var parentID = yield parentCol.saveTx();
|
2015-05-04 09:59:40 +00:00
|
|
|
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
|
|
|
|
|
|
|
|
// Create subcollection
|
|
|
|
var col = new Zotero.Collection
|
|
|
|
col.name = "Child";
|
|
|
|
col.parentKey = parentKey;
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield col.saveTx();
|
2015-05-04 09:59:40 +00:00
|
|
|
|
|
|
|
// Create new parent collection
|
|
|
|
var newParentCol = new Zotero.Collection
|
|
|
|
newParentCol.name = "New Parent";
|
2015-05-10 08:20:47 +00:00
|
|
|
var newParentID = yield newParentCol.saveTx();
|
2015-05-04 09:59:40 +00:00
|
|
|
var {libraryID, key: newParentKey} = Zotero.Collections.getLibraryAndKeyFromID(newParentID);
|
|
|
|
|
|
|
|
// Change parent collection
|
|
|
|
col.parentKey = newParentKey;
|
2015-05-10 08:20:47 +00:00
|
|
|
yield col.saveTx();
|
2015-05-21 07:19:28 +00:00
|
|
|
assert.equal(col.parentKey, newParentKey);
|
2015-05-04 09:59:40 +00:00
|
|
|
col = yield Zotero.Collections.getAsync(id);
|
|
|
|
assert.equal(col.parentKey, newParentKey);
|
|
|
|
});
|
|
|
|
|
2015-05-05 18:08:28 +00:00
|
|
|
it("should not mark collection as unchanged if set to existing value", function* () {
|
|
|
|
// Create initial parent collection
|
|
|
|
var parentCol = new Zotero.Collection
|
|
|
|
parentCol.name = "Parent";
|
2015-05-10 08:20:47 +00:00
|
|
|
var parentID = yield parentCol.saveTx();
|
2015-05-05 18:08:28 +00:00
|
|
|
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
|
|
|
|
|
|
|
|
// Create subcollection
|
|
|
|
var col = new Zotero.Collection
|
|
|
|
col.name = "Child";
|
|
|
|
col.parentKey = parentKey;
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield col.saveTx();
|
2015-05-05 18:08:28 +00:00
|
|
|
|
|
|
|
// Set to existing parent
|
|
|
|
col.parentKey = parentKey;
|
|
|
|
assert.isFalse(col.hasChanged());
|
|
|
|
});
|
|
|
|
|
2015-05-04 09:59:40 +00:00
|
|
|
it("should not resave a collection with no parent if set to false", function* () {
|
|
|
|
var col = new Zotero.Collection
|
|
|
|
col.name = "Test";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield col.saveTx();
|
2015-05-04 09:59:40 +00:00
|
|
|
|
|
|
|
col.parentKey = false;
|
2015-05-10 08:20:47 +00:00
|
|
|
var ret = yield col.saveTx();
|
2015-05-04 09:59:40 +00:00
|
|
|
assert.isFalse(ret);
|
|
|
|
});
|
|
|
|
})
|
2015-05-30 22:35:43 +00:00
|
|
|
|
2015-08-08 20:45:51 +00:00
|
|
|
describe("#hasChildCollections()", function () {
|
|
|
|
it("should be false if child made top-level", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection');
|
|
|
|
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
|
|
|
|
|
|
|
|
assert.isTrue(collection1.hasChildCollections());
|
|
|
|
collection2.parentKey = false;
|
|
|
|
yield collection2.saveTx();
|
|
|
|
assert.isFalse(collection1.hasChildCollections());
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should be false if child moved to another collection", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection');
|
|
|
|
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
|
|
|
|
var collection3 = yield createDataObject('collection');
|
|
|
|
|
|
|
|
assert.isTrue(collection1.hasChildCollections());
|
|
|
|
collection2.parentKey = collection3.key;
|
|
|
|
yield collection2.saveTx();
|
|
|
|
assert.isFalse(collection1.hasChildCollections());
|
2021-01-13 05:40:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("should return false if all child collections are moved to trash", async function () {
|
|
|
|
var collection1 = await createDataObject('collection');
|
|
|
|
var collection2 = await createDataObject('collection', { parentID: collection1.id });
|
|
|
|
var collection3 = await createDataObject('collection', { parentID: collection1.id });
|
|
|
|
|
|
|
|
assert.isTrue(collection1.hasChildCollections());
|
|
|
|
collection2.deleted = true;
|
|
|
|
await collection2.saveTx();
|
|
|
|
assert.isTrue(collection1.hasChildCollections());
|
|
|
|
collection3.deleted = true;
|
|
|
|
await collection3.saveTx();
|
|
|
|
assert.isFalse(collection1.hasChildCollections());
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should return true if child collection is in trash and includeTrashed is true", async function () {
|
|
|
|
var collection1 = await createDataObject('collection');
|
|
|
|
var collection2 = await createDataObject('collection', { parentID: collection1.id });
|
|
|
|
|
|
|
|
assert.isTrue(collection1.hasChildCollections(true));
|
|
|
|
collection2.deleted = true;
|
|
|
|
await collection2.saveTx();
|
|
|
|
assert.isTrue(collection1.hasChildCollections(true));
|
|
|
|
});
|
2015-08-08 20:45:51 +00:00
|
|
|
})
|
|
|
|
|
2015-05-30 22:35:43 +00:00
|
|
|
describe("#getChildCollections()", function () {
|
|
|
|
it("should include child collections", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection');
|
|
|
|
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
|
|
|
|
yield collection1.saveTx();
|
|
|
|
|
|
|
|
var childCollections = collection1.getChildCollections();
|
|
|
|
assert.lengthOf(childCollections, 1);
|
|
|
|
assert.equal(childCollections[0].id, collection2.id);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should not include collections that have been removed", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection');
|
|
|
|
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
|
|
|
|
yield collection1.saveTx();
|
|
|
|
|
|
|
|
collection2.parentID = false;
|
|
|
|
yield collection2.save()
|
|
|
|
|
|
|
|
var childCollections = collection1.getChildCollections();
|
|
|
|
assert.lengthOf(childCollections, 0);
|
|
|
|
})
|
2017-02-03 06:17:08 +00:00
|
|
|
|
|
|
|
it("should not include collections that have been deleted", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection');
|
|
|
|
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
|
|
|
|
yield collection1.saveTx();
|
|
|
|
|
|
|
|
yield collection2.eraseTx()
|
|
|
|
|
|
|
|
var childCollections = collection1.getChildCollections();
|
|
|
|
assert.lengthOf(childCollections, 0);
|
|
|
|
})
|
2015-05-30 22:35:43 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
describe("#getChildItems()", function () {
|
|
|
|
it("should include child items", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
item.addToCollection(collection.key);
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
assert.lengthOf(collection.getChildItems(), 1);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should not include items in trash by default", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
item.deleted = true;
|
|
|
|
item.addToCollection(collection.key);
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
assert.lengthOf(collection.getChildItems(), 0);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should include items in trash if includeTrashed=true", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
item.deleted = true;
|
|
|
|
item.addToCollection(collection.key);
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
assert.lengthOf(collection.getChildItems(false, true), 1);
|
|
|
|
})
|
2017-03-07 06:54:49 +00:00
|
|
|
|
|
|
|
it("should not include removed items", function* () {
|
|
|
|
var col = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [ col.id ] });
|
|
|
|
assert.lengthOf(col.getChildItems(), 1);
|
|
|
|
item.setCollections([]);
|
|
|
|
yield item.saveTx();
|
|
|
|
Zotero.debug(col.getChildItems());
|
|
|
|
assert.lengthOf(col.getChildItems(), 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should not include deleted items", function* () {
|
|
|
|
var col = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [ col.id ] });
|
|
|
|
assert.lengthOf(col.getChildItems(), 1);
|
|
|
|
yield item.erase();
|
2017-09-18 21:03:12 +00:00
|
|
|
assert.lengthOf(col.getChildItems(), 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should not include items emptied from trash", function* () {
|
|
|
|
var col = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [ col.id ], deleted: true });
|
|
|
|
yield item.erase();
|
2017-03-07 06:54:49 +00:00
|
|
|
assert.lengthOf(col.getChildItems(), 0);
|
|
|
|
});
|
2015-05-30 22:35:43 +00:00
|
|
|
})
|
2016-03-25 22:40:47 +00:00
|
|
|
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
describe("#fromJSON()", function () {
|
|
|
|
it("should ignore unknown property in non-strict mode", function () {
|
|
|
|
var json = {
|
|
|
|
name: "Collection",
|
|
|
|
foo: "Bar"
|
|
|
|
};
|
|
|
|
var s = new Zotero.Collection();
|
|
|
|
s.fromJSON(json);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should throw on unknown property in strict mode", function () {
|
|
|
|
var json = {
|
|
|
|
name: "Collection",
|
|
|
|
foo: "Bar"
|
|
|
|
};
|
|
|
|
var s = new Zotero.Collection();
|
|
|
|
var f = () => {
|
|
|
|
s.fromJSON(json, { strict: true });
|
|
|
|
};
|
|
|
|
assert.throws(f, /^Unknown collection property/);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-05-15 07:34:06 +00:00
|
|
|
describe("#toJSON()", function () {
|
|
|
|
it("should set 'parentCollection' to false when cleared", function* () {
|
|
|
|
var col1 = yield createDataObject('collection');
|
|
|
|
var col2 = yield createDataObject('collection', { parentID: col1.id });
|
|
|
|
// Create initial JSON with parentCollection
|
|
|
|
var patchBase = col2.toJSON();
|
|
|
|
// Clear parent collection and regenerate JSON
|
|
|
|
col2.parentID = false;
|
2024-01-10 04:41:32 +00:00
|
|
|
yield col2.saveTx();
|
2016-05-15 07:34:06 +00:00
|
|
|
var json = col2.toJSON({ patchBase });
|
|
|
|
assert.isFalse(json.parentCollection);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-03-25 22:40:47 +00:00
|
|
|
describe("#getDescendents()", function () {
|
|
|
|
var collection0, collection1, collection2, collection3, item1, item2, item3;
|
|
|
|
|
|
|
|
before(function* () {
|
|
|
|
collection0 = yield createDataObject('collection');
|
|
|
|
item1 = yield createDataObject('item', { collections: [collection0.id] });
|
|
|
|
collection1 = yield createDataObject('collection', { parentKey: collection0.key });
|
|
|
|
item2 = yield createDataObject('item', { collections: [collection1.id] });
|
|
|
|
collection2 = yield createDataObject('collection', { parentKey: collection1.key });
|
|
|
|
collection3 = yield createDataObject('collection', { parentKey: collection1.key });
|
|
|
|
item3 = yield createDataObject('item', { collections: [collection2.id] });
|
|
|
|
item3.deleted = true;
|
|
|
|
yield item3.saveTx();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should return a flat array of collections and items", function* () {
|
|
|
|
var desc = collection0.getDescendents();
|
|
|
|
assert.lengthOf(desc, 5);
|
|
|
|
assert.sameMembers(
|
|
|
|
desc.map(x => x.type + ':' + x.id + ':' + (x.name || '') + ':' + x.parent),
|
|
|
|
[
|
|
|
|
'item:' + item1.id + '::' + collection0.id,
|
|
|
|
'item:' + item2.id + '::' + collection1.id,
|
|
|
|
'collection:' + collection1.id + ':' + collection1.name + ':' + collection0.id,
|
|
|
|
'collection:' + collection2.id + ':' + collection2.name + ':' + collection1.id,
|
|
|
|
'collection:' + collection3.id + ':' + collection3.name + ':' + collection1.id
|
|
|
|
]
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should return nested arrays of collections and items", function* () {
|
|
|
|
var desc = collection0.getDescendents(true);
|
|
|
|
assert.lengthOf(desc, 2);
|
|
|
|
assert.sameMembers(
|
|
|
|
desc.map(x => x.type + ':' + x.id + ':' + (x.name || '') + ':' + x.parent),
|
|
|
|
[
|
|
|
|
'item:' + item1.id + '::' + collection0.id,
|
|
|
|
'collection:' + collection1.id + ':' + collection1.name + ':' + collection0.id,
|
|
|
|
]
|
|
|
|
);
|
|
|
|
var c = desc[0].type == 'collection' ? desc[0] : desc[1];
|
|
|
|
assert.lengthOf(c.children, 3);
|
|
|
|
assert.sameMembers(
|
|
|
|
c.children.map(x => x.type + ':' + x.id + ':' + (x.name || '') + ':' + x.parent),
|
|
|
|
[
|
|
|
|
'item:' + item2.id + '::' + collection1.id,
|
|
|
|
'collection:' + collection2.id + ':' + collection2.name + ':' + collection1.id,
|
|
|
|
'collection:' + collection3.id + ':' + collection3.name + ':' + collection1.id
|
|
|
|
]
|
|
|
|
);
|
|
|
|
});
|
2017-03-07 06:54:49 +00:00
|
|
|
|
|
|
|
it("should not include deleted items", function* () {
|
|
|
|
var col = yield createDataObject('collection');
|
|
|
|
var item = yield createDataObject('item', { collections: [col.id] });
|
|
|
|
assert.lengthOf(col.getDescendents(), 1);
|
|
|
|
yield item.eraseTx();
|
|
|
|
assert.lengthOf(col.getDescendents(), 0);
|
|
|
|
});
|
|
|
|
|
2016-03-25 22:40:47 +00:00
|
|
|
});
|
2015-04-26 06:44:29 +00:00
|
|
|
})
|