zotero/test/tests/collectionTest.js
Dan Stillman 4b60c6ca27 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-09-16 02:27:22 -04:00

362 lines
13 KiB
JavaScript

"use strict";
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;
var id = yield collection.saveTx();
assert.equal(collection.name, name);
collection = yield Zotero.Collections.getAsync(id);
assert.equal(collection.name, name);
});
})
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);
});
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);
});
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);
});
})
describe("#version", function () {
it("should set object version", function* () {
var version = 100;
var collection = new Zotero.Collection
collection.version = version;
collection.name = "Test";
var id = yield collection.saveTx();
assert.equal(collection.version, version);
collection = yield Zotero.Collections.getAsync(id);
assert.equal(collection.version, version);
});
})
describe("#parentKey", function () {
it("should set parent collection for new collections", function* () {
var parentCol = new Zotero.Collection
parentCol.name = "Parent";
var parentID = yield parentCol.saveTx();
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
var col = new Zotero.Collection
col.name = "Child";
col.parentKey = parentKey;
var id = yield col.saveTx();
assert.equal(col.parentKey, parentKey);
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";
var parentID = yield parentCol.saveTx();
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
// Create subcollection
var col = new Zotero.Collection
col.name = "Child";
col.parentKey = parentKey;
var id = yield col.saveTx();
// Create new parent collection
var newParentCol = new Zotero.Collection
newParentCol.name = "New Parent";
var newParentID = yield newParentCol.saveTx();
var {libraryID, key: newParentKey} = Zotero.Collections.getLibraryAndKeyFromID(newParentID);
// Change parent collection
col.parentKey = newParentKey;
yield col.saveTx();
assert.equal(col.parentKey, newParentKey);
col = yield Zotero.Collections.getAsync(id);
assert.equal(col.parentKey, newParentKey);
});
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";
var parentID = yield parentCol.saveTx();
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
// Create subcollection
var col = new Zotero.Collection
col.name = "Child";
col.parentKey = parentKey;
var id = yield col.saveTx();
// Set to existing parent
col.parentKey = parentKey;
assert.isFalse(col.hasChanged());
});
it("should not resave a collection with no parent if set to false", function* () {
var col = new Zotero.Collection
col.name = "Test";
var id = yield col.saveTx();
col.parentKey = false;
var ret = yield col.saveTx();
assert.isFalse(ret);
});
})
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());
})
})
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);
})
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);
})
})
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);
})
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();
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();
assert.lengthOf(col.getChildItems(), 0);
});
})
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/);
});
});
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;
var json = col2.toJSON({ patchBase });
assert.isFalse(json.parentCollection);
});
});
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
]
);
});
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);
});
});
})