zotero/test/tests/collectionTest.js
Bogdan Abaev a532cfb475 trash functionality for collections and searches (#3307)
When a collection or a saved search is deleted, it appears in
trash among other trashed items. From there, it can be restored
or permanently deleted.

Items of trashed collections are not affected my the trashing/permanent
deletion of a collection and need to be deleted separately like before.

Subcollections of a trashed collection do not appear in the trash and
are restored or permanently deleted with the top-most trashed parent.
2024-06-17 23:14:21 -04:00

454 lines
17 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);
});
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);
});
it("should send deleted collections to trash", async function () {
var collection1 = await createDataObject('collection');
var collection2 = await createDataObject('collection', { parentID: collection1.id });
var collection3 = await createDataObject('collection', { parentID: collection2.id });
collection1.deleted = true;
await collection1.saveTx();
var deleted = await Zotero.Collections.getDeleted(collection1.libraryID, true);
assert.include(deleted, collection1.id);
assert.include(deleted, collection2.id);
assert.include(deleted, collection3.id);
});
it("should restore deleted collection", async function () {
var collection1 = await createDataObject('collection');
var item1 = await createDataObject('item', { collections: [collection1.id] });
assert.include(item1.getCollections(), collection1.id);
collection1.deleted = true;
await collection1.saveTx();
// Deleted collection is gone from item's cache
assert.notInclude(item1.getCollections(), collection1.id);
// Restore deleted collection
collection1.deleted = false;
await collection1.saveTx();
var deleted = await Zotero.Collections.getDeleted(collection1.libraryID, true);
// Collection is restored from trash
assert.notInclude(deleted, collection1.id);
// Collection is back in item's cache
assert.include(item1.getCollections(), collection1.id);
});
it("should permanently delete collections from trash", 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();
assert.equal(await Zotero.Collections.getAsync(collection1.id), false);
assert.equal(await Zotero.Collections.getAsync(collection2.id), false);
assert.equal(await Zotero.Collections.getAsync(collection3.id), false);
});
})
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());
});
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));
});
})
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;
yield col2.saveTx();
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);
});
});
})