zotero/test/tests/itemsTest.js

627 lines
20 KiB
JavaScript

describe("Zotero.Items", function () {
var win, collectionsView, zp;
before(function* () {
this.timeout(10000);
win = yield loadZoteroPane();
collectionsView = win.ZoteroPane.collectionsView;
zp = win.ZoteroPane;
})
beforeEach(function () {
return selectLibrary(win);
})
after(function () {
win.close();
})
describe("#addToPublications", function () {
it("should add an item to My Publications", function* () {
var item = yield createDataObject('item');
yield Zotero.Items.addToPublications([item]);
assert.isTrue(item.inPublications);
assert.equal(
(yield Zotero.DB.valueQueryAsync(
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
1
);
});
describe("#license", function () {
it("should set a license if specified", function* () {
var item = createUnsavedDataObject('item');
item.setField('rights', 'Test');
yield item.saveTx();
yield Zotero.Items.addToPublications(
[item],
{
license: 'reserved',
licenseName: 'All Rights Reserved',
keepRights: false
}
);
assert.equal(item.getField('rights'), 'All Rights Reserved');
});
it("should keep existing Rights field if .keepRights is true", function* () {
var item1 = createUnsavedDataObject('item');
item1.setField('rights', 'Test');
yield item1.saveTx();
var item2 = yield createDataObject('item');
yield Zotero.Items.addToPublications(
[item1, item2],
{
license: 'reserved',
licenseName: 'All Rights Reserved',
keepRights: true
}
);
assert.equal(item1.getField('rights'), 'Test');
assert.equal(item2.getField('rights'), 'All Rights Reserved');
});
it("shouldn't set a license if not specified", function* () {
var item = createUnsavedDataObject('item');
item.setField('rights', 'Test');
yield item.saveTx();
yield Zotero.Items.addToPublications([item]);
assert.equal(item.getField('rights'), 'Test');
});
});
it("should add child notes if .childNotes is true", function* () {
var item = yield createDataObject('item');
var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
var attachment = yield Zotero.Attachments.linkFromURL({
url: "http://example.com",
parentItemID: item.id,
title: "Example"
});
yield Zotero.Items.addToPublications([item], { childNotes: true });
assert.isTrue(note.inPublications);
assert.equal(
(yield Zotero.DB.valueQueryAsync(
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", note.id)),
1
);
assert.isFalse(attachment.inPublications);
});
it("should add child link attachments if .childLinks is true", function* () {
var item = yield createDataObject('item');
var attachment1 = yield Zotero.Attachments.linkFromURL({
url: "http://example.com",
parentItemID: item.id,
title: "Example"
});
var attachment2 = yield importFileAttachment('test.png', { parentItemID: item.id });
var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
yield Zotero.Items.addToPublications([item], { childLinks: true });
assert.isTrue(attachment1.inPublications);
assert.equal(
(yield Zotero.DB.valueQueryAsync(
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", attachment1.id)),
1
);
assert.isFalse(attachment2.inPublications);
assert.isFalse(note.inPublications);
});
it("should add child file attachments if .childFileAttachments is true", function* () {
var item = yield createDataObject('item');
var attachment1 = yield importFileAttachment('test.png', { parentItemID: item.id });
var attachment2 = yield Zotero.Attachments.linkFromURL({
url: "http://example.com",
parentItemID: item.id,
title: "Example"
});
var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
yield Zotero.Items.addToPublications([item], { childFileAttachments: true });
assert.isTrue(attachment1.inPublications);
assert.equal(
(yield Zotero.DB.valueQueryAsync(
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", attachment1.id)),
1
);
assert.isFalse(attachment2.inPublications);
assert.isFalse(note.inPublications);
});
});
describe("#removeFromPublications", function () {
it("should remove an item from My Publications", function* () {
var item = yield createDataObject('item');
item.inPublications = true;
yield item.saveTx();
assert.equal(
(yield Zotero.DB.valueQueryAsync(
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
1
);
yield Zotero.Items.removeFromPublications([item]);
assert.isFalse(item.inPublications);
assert.equal(
(yield Zotero.DB.valueQueryAsync(
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
0
);
});
});
describe("#merge()", function () {
it("should merge two items", function* () {
var item1 = yield createDataObject('item');
var item2 = yield createDataObject('item');
var item2URI = Zotero.URI.getItemURI(item2);
yield Zotero.Items.merge(item1, [item2]);
assert.isFalse(item1.deleted);
assert.isTrue(item2.deleted);
// Check for merge-tracking relation
assert.isFalse(item1.hasChanged());
var rels = item1.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate);
assert.lengthOf(rels, 1);
assert.equal(rels[0], item2URI);
})
it("should merge three items", async function () {
var item1 = await createDataObject('item');
var item2 = await createDataObject('item');
var item3 = await createDataObject('item');
var item2URI = Zotero.URI.getItemURI(item2);
var item3URI = Zotero.URI.getItemURI(item3);
await Zotero.Items.merge(item1, [item2, item3]);
assert.isFalse(item1.deleted);
assert.isTrue(item2.deleted);
assert.isTrue(item3.deleted);
// Check for merge-tracking relation
assert.isFalse(item1.hasChanged());
var rels = item1.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate);
assert.lengthOf(rels, 2);
assert.sameMembers(rels, [item2URI, item3URI]);
})
it("should use the earliest Date Added", async function () {
var item1 = await createDataObject('item', { dateAdded: '2019-01-02 00:00:00' });
var item2 = await createDataObject('item', { dateAdded: '2019-01-01 00:00:00' });
var item3 = await createDataObject('item', { dateAdded: '2019-01-03 00:00:00' });
await Zotero.Items.merge(item1, [item2, item3]);
assert.equal(item1.dateAdded, '2019-01-01 00:00:00');
});
it("should keep automatic tag on non-master item as automatic", async function () {
var item1 = await createDataObject('item', { tags: [{ tag: 'A' }] });
var item2 = await createDataObject('item', { tags: [{ tag: 'B', type: 1 }] });
await Zotero.Items.merge(item1, [item2]);
var tags = item1.getTags();
var tag = tags.find(x => x.tag == 'B');
assert.propertyVal(tag, 'type', 1);
});
it("should skip automatic tag on non-master item that exists as manual tag on master", async function () {
var item1 = await createDataObject('item', { tags: [{ tag: 'A' }, { tag: 'B' }] });
var item2 = await createDataObject('item', { tags: [{ tag: 'B', type: 1 }] });
await Zotero.Items.merge(item1, [item2]);
var tags = item1.getTags();
var tag = tags.find(x => x.tag == 'B');
assert.notProperty(tag, 'type');
});
it("should keep automatic tag on master if it also exists on non-master item", async function () {
var item1 = await createDataObject('item', { tags: [{ tag: 'B', type: 1 }] });
var item2 = await createDataObject('item', { tags: [{ tag: 'B', type: 1 }] });
await Zotero.Items.merge(item1, [item2]);
var tags = item1.getTags();
assert.propertyVal(tags[0], 'type', 1);
});
it("should merge two items when servant is linked to an item absent from cache", function* () {
// two group libraries
var groupOneInfo = yield createGroup({
id: 25026,
name: "Group One"
});
var libraryOneID = Zotero.Groups.getLibraryIDFromGroupID(groupOneInfo.id);
var groupTwoInfo = yield createGroup({
id: 11592,
name: "Group Two"
});
var libraryTwoID = Zotero.Groups.getLibraryIDFromGroupID(groupTwoInfo.id);
assert.notEqual(libraryOneID, libraryTwoID);
// two items in the first library
var item1 = yield createDataObject('item', {libraryID: libraryOneID});
var item2 = yield createDataObject('item', {libraryID: libraryOneID});
var item2URI = Zotero.URI.getItemURI(item2);
// one item in the second library, linked to item2 as if it dragged and dropped from it
var itemX = yield createDataObject('item', {libraryID: libraryTwoID});
yield itemX.addLinkedItem(item2);
// check that the owl:sameAs relation has been registered okay
var rels = itemX.getRelationsByPredicate(Zotero.Relations.linkedObjectPredicate);
assert.lengthOf(rels, 1);
assert.equal(rels[0], item2URI);
// the freshly minted item is in objectCache, but it might be absent in production,
// so we clobber it in this test
assert(!!Zotero.Items._objectCache[itemX.id], "itemX is in object cache")
delete Zotero.Items._objectCache[itemX.id];
// merge the two items in the first library
yield Zotero.Items.merge(item1, [item2]);
// check that the merge completed okay
assert.isFalse(item1.deleted);
assert.isTrue(item2.deleted);
// Check for merge-tracking relation
assert.isFalse(item1.hasChanged());
var rels = item1.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate);
assert.lengthOf(rels, 1);
assert.equal(rels[0], item2URI);
})
it("should move merge-tracking relation from replaced item to master", function* () {
var item1 = yield createDataObject('item');
var item2 = yield createDataObject('item');
var item2URI = Zotero.URI.getItemURI(item2);
var item3 = yield createDataObject('item');
var item3URI = Zotero.URI.getItemURI(item3);
yield Zotero.Items.merge(item2, [item3]);
yield Zotero.Items.merge(item1, [item2]);
// Check for merge-tracking relation from 1 to 3
var rels = item1.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate);
assert.lengthOf(rels, 2);
assert.sameMembers(rels, [item2URI, item3URI]);
})
// Same as test in itemPaneTest, but without the UI
it("should transfer merge-tracking relations when merging two pairs into one item", async function () {
var item1 = await createDataObject('item', { title: 'A' });
var item2 = await createDataObject('item', { title: 'B' });
var item3 = await createDataObject('item', { title: 'C' });
var item4 = await createDataObject('item', { title: 'D' });
var uris = [item2, item3, item4].map(item => Zotero.URI.getItemURI(item));
await Zotero.Items.merge(item1, [item2]);
await Zotero.Items.merge(item3, [item4]);
await Zotero.Items.merge(item1, [item3]);
// Remaining item should include all other URIs
assert.sameMembers(
item1.getRelations()[Zotero.Relations.replacedItemPredicate],
uris
);
});
it("should update relations pointing to replaced item to point to master", function* () {
var item1 = yield createDataObject('item');
var item1URI = Zotero.URI.getItemURI(item1);
var item2 = yield createDataObject('item');
var item2URI = Zotero.URI.getItemURI(item2);
var item3 = createUnsavedDataObject('item');
var predicate = Zotero.Relations.relatedItemPredicate;
item3.addRelation(predicate, item2URI);
yield item3.saveTx();
yield Zotero.Items.merge(item1, [item2]);
// Check for related-item relation from 3 to 1
var rels = item3.getRelationsByPredicate(predicate);
assert.deepEqual(rels, [item1URI]);
})
it("should not update relations pointing to replaced item in other libraries", function* () {
var group1 = yield createGroup();
var group2 = yield createGroup();
var item1 = yield createDataObject('item', { libraryID: group1.libraryID });
var item1URI = Zotero.URI.getItemURI(item1);
var item2 = yield createDataObject('item', { libraryID: group1.libraryID });
var item2URI = Zotero.URI.getItemURI(item2);
var item3 = createUnsavedDataObject('item', { libraryID: group2.libraryID });
var predicate = Zotero.Relations.linkedObjectPredicate;
item3.addRelation(predicate, item2URI);
yield item3.saveTx();
yield Zotero.Items.merge(item1, [item2]);
// Check for related-item relation from 3 to 2
var rels = item3.getRelationsByPredicate(predicate);
assert.deepEqual(rels, [item2URI]);
})
})
describe("#trash()", function () {
it("should send items to the trash", function* () {
var items = [];
items.push(
(yield createDataObject('item', { synced: true })),
(yield createDataObject('item', { synced: true })),
(yield createDataObject('item', { synced: true }))
);
items.forEach(item => {
// Sanity-checked as true in itemTest#deleted
assert.isUndefined(item._changed.deleted);
});
var ids = items.map(item => item.id);
yield Zotero.Items.trashTx(ids);
items.forEach(item => {
assert.isTrue(item.deleted);
// Item should be saved (can't use hasChanged() because that includes .synced)
assert.isUndefined(item._changed.deleted);
assert.isFalse(item.synced);
});
assert.equal((yield Zotero.DB.valueQueryAsync(
`SELECT COUNT(*) FROM deletedItems WHERE itemID IN (${ids})`
)), 3);
for (let item of items) {
assert.equal((yield Zotero.DB.valueQueryAsync(
`SELECT synced FROM items WHERE itemID=${item.id}`
)), 0);
}
});
it("should update parent item when trashing child item", function* () {
var item = yield createDataObject('item');
var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
assert.lengthOf(item.getNotes(), 1);
yield Zotero.Items.trashTx([note.id]);
assert.lengthOf(item.getNotes(), 0);
});
});
describe("#emptyTrash()", function () {
it("should delete items in the trash", function* () {
var item1 = createUnsavedDataObject('item');
item1.setField('title', 'a');
item1.deleted = true;
var id1 = yield item1.saveTx();
var item2 = createUnsavedDataObject('item');
item2.setField('title', 'b');
item2.deleted = true;
var id2 = yield item2.saveTx();
var item3 = createUnsavedDataObject('item', { itemType: 'attachment', parentID: id2 });
item3.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL;
item3.deleted = true;
var id3 = yield item3.saveTx();
yield collectionsView.selectTrash(Zotero.Libraries.userLibraryID);
yield Zotero.Items.emptyTrash(Zotero.Libraries.userLibraryID);
assert.isFalse(yield Zotero.Items.getAsync(id1));
assert.isFalse(yield Zotero.Items.getAsync(id2));
assert.isFalse(yield Zotero.Items.getAsync(id3));
// TEMP: This is failing on Travis due to a race condition
//assert.equal(zp.itemsView.rowCount, 0)
})
})
describe("#getFirstCreatorFromData()", function () {
it("should handle single eligible creator", function* () {
for (let creatorType of ['author', 'editor', 'contributor']) {
assert.equal(
Zotero.Items.getFirstCreatorFromData(
Zotero.ItemTypes.getID('book'),
[
{
fieldMode: 0,
firstName: 'A',
lastName: 'B',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
}
]
),
'B',
creatorType
);
}
});
it("should ignore single ineligible creator", function* () {
assert.strictEqual(
Zotero.Items.getFirstCreatorFromData(
Zotero.ItemTypes.getID('book'),
[
{
fieldMode: 0,
firstName: 'A',
lastName: 'B',
creatorTypeID: Zotero.CreatorTypes.getID('translator')
}
]
),
''
);
});
it("should handle single eligible creator after ineligible creator", function* () {
for (let creatorType of ['author', 'editor', 'contributor']) {
assert.equal(
Zotero.Items.getFirstCreatorFromData(
Zotero.ItemTypes.getID('book'),
[
{
fieldMode: 0,
firstName: 'A',
lastName: 'B',
creatorTypeID: Zotero.CreatorTypes.getID('translator')
},
{
fieldMode: 0,
firstName: 'C',
lastName: 'D',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
}
]
),
'D',
creatorType
);
}
});
it("should handle two eligible creators", function* () {
for (let creatorType of ['author', 'editor', 'contributor']) {
assert.equal(
Zotero.Items.getFirstCreatorFromData(
Zotero.ItemTypes.getID('book'),
[
{
fieldMode: 0,
firstName: 'A',
lastName: 'B',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
},
{
fieldMode: 0,
firstName: 'C',
lastName: 'D',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
}
]
),
'B ' + Zotero.getString('general.and') + ' D',
creatorType
);
}
});
it("should handle three eligible creators", function* () {
for (let creatorType of ['author', 'editor', 'contributor']) {
assert.equal(
Zotero.Items.getFirstCreatorFromData(
Zotero.ItemTypes.getID('book'),
[
{
fieldMode: 0,
firstName: 'A',
lastName: 'B',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
},
{
fieldMode: 0,
firstName: 'C',
lastName: 'D',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
},
{
fieldMode: 0,
firstName: 'E',
lastName: 'F',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
}
]
),
'B ' + Zotero.getString('general.etAl'),
creatorType
);
}
});
it("should handle two eligible creators with intervening creators", function* () {
for (let creatorType of ['author', 'editor', 'contributor']) {
assert.equal(
Zotero.Items.getFirstCreatorFromData(
Zotero.ItemTypes.getID('book'),
[
{
fieldMode: 0,
firstName: 'A',
lastName: 'B',
creatorTypeID: Zotero.CreatorTypes.getID('translator')
},
{
fieldMode: 0,
firstName: 'C',
lastName: 'D',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
},
{
fieldMode: 0,
firstName: 'E',
lastName: 'F',
creatorTypeID: Zotero.CreatorTypes.getID('translator')
},
{
fieldMode: 0,
firstName: 'G',
lastName: 'H',
creatorTypeID: Zotero.CreatorTypes.getID(creatorType)
}
]
),
'D ' + Zotero.getString('general.and') + ' H',
creatorType
);
}
});
});
describe("#getAsync()", function() {
it("should return Zotero.Item for item ID", function* () {
let item = new Zotero.Item('journalArticle');
let id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
assert.notOk(item.isFeedItem);
assert.instanceOf(item, Zotero.Item);
assert.notInstanceOf(item, Zotero.FeedItem);
});
it("should return Zotero.FeedItem for feed item ID", function* () {
let feed = new Zotero.Feed({ name: 'foo', url: 'http://www.' + Zotero.randomString() + '.com' });
yield feed.saveTx();
let feedItem = new Zotero.FeedItem('journalArticle', { guid: Zotero.randomString() });
feedItem.libraryID = feed.libraryID;
let id = yield feedItem.saveTx();
feedItem = yield Zotero.Items.getAsync(id);
assert.isTrue(feedItem.isFeedItem);
assert.instanceOf(feedItem, Zotero.FeedItem);
});
});
describe("#keepParents()", function () {
it("should remove child items of passed items", async function () {
var item1 = await createDataObject('item');
var item2 = await createDataObject('item', { itemType: 'note', parentItemID: item1.id });
var item3 = await createDataObject('item', { itemType: 'note', parentItemID: item1.id });
var item4 = await createDataObject('item');
var item5 = await createDataObject('item', { itemType: 'note', parentItemID: item4.id });
var otherItem = await createDataObject('item');
var item6 = await createDataObject('item', { itemType: 'note', parentItemID: otherItem.id });
var items = Zotero.Items.keepParents([item1, item2, item3, item4, item5, item6]);
assert.sameMembers(
// Convert to ids for clearer output
items.map(item => item.id),
[item1, item4, item6].map(item => item.id)
);
});
});
});