2015-05-25 02:57:46 +00:00
|
|
|
describe("Zotero.Items", function () {
|
2015-05-25 05:43:07 +00:00
|
|
|
var win, collectionsView, zp;
|
2015-05-25 02:57:46 +00:00
|
|
|
|
|
|
|
before(function* () {
|
2015-06-02 04:29:40 +00:00
|
|
|
this.timeout(10000);
|
2015-05-25 02:57:46 +00:00
|
|
|
win = yield loadZoteroPane();
|
|
|
|
collectionsView = win.ZoteroPane.collectionsView;
|
2015-05-25 05:43:07 +00:00
|
|
|
zp = win.ZoteroPane;
|
2015-05-25 02:57:46 +00:00
|
|
|
})
|
|
|
|
beforeEach(function () {
|
|
|
|
return selectLibrary(win);
|
|
|
|
})
|
|
|
|
after(function () {
|
|
|
|
win.close();
|
|
|
|
})
|
|
|
|
|
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("#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
|
2016-04-30 00:25:11 +00:00
|
|
|
assert.isFalse(item1.hasChanged());
|
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
|
|
|
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]);
|
|
|
|
})
|
|
|
|
|
|
|
|
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]);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2017-02-09 02:53:29 +00:00
|
|
|
|
|
|
|
describe("#trash()", function () {
|
|
|
|
it("should send items to the trash", function* () {
|
|
|
|
var items = [];
|
|
|
|
items.push(
|
2017-02-15 18:15:30 +00:00
|
|
|
(yield createDataObject('item', { synced: true })),
|
|
|
|
(yield createDataObject('item', { synced: true })),
|
|
|
|
(yield createDataObject('item', { synced: true }))
|
2017-02-09 02:53:29 +00:00
|
|
|
);
|
2017-02-15 18:15:30 +00:00
|
|
|
items.forEach(item => {
|
|
|
|
// Sanity-checked as true in itemTest#deleted
|
|
|
|
assert.isUndefined(item._changed.deleted);
|
|
|
|
});
|
2017-02-09 02:53:29 +00:00
|
|
|
var ids = items.map(item => item.id);
|
|
|
|
yield Zotero.Items.trashTx(ids);
|
|
|
|
items.forEach(item => {
|
|
|
|
assert.isTrue(item.deleted);
|
2017-02-15 18:15:30 +00:00
|
|
|
// Item should be saved (can't use hasChanged() because that includes .synced)
|
|
|
|
assert.isUndefined(item._changed.deleted);
|
|
|
|
assert.isFalse(item.synced);
|
2017-02-09 02:53:29 +00:00
|
|
|
});
|
|
|
|
assert.equal((yield Zotero.DB.valueQueryAsync(
|
|
|
|
`SELECT COUNT(*) FROM deletedItems WHERE itemID IN (${ids})`
|
|
|
|
)), 3);
|
2017-02-15 18:15:30 +00:00
|
|
|
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);
|
2017-02-09 02:53:29 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2015-05-25 02:57:46 +00:00
|
|
|
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));
|
2015-05-31 21:17:36 +00:00
|
|
|
|
2015-06-07 21:16:31 +00:00
|
|
|
// TEMP: This is failing on Travis due to a race condition
|
|
|
|
//assert.equal(zp.itemsView.rowCount, 0)
|
2015-05-25 02:57:46 +00:00
|
|
|
})
|
|
|
|
})
|
2015-06-02 04:29:40 +00:00
|
|
|
|
2017-02-16 04:13:35 +00:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-06-02 04:29:40 +00:00
|
|
|
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;
|
2016-02-11 11:02:38 +00:00
|
|
|
let id = yield feedItem.saveTx();
|
2015-06-02 04:29:40 +00:00
|
|
|
|
|
|
|
feedItem = yield Zotero.Items.getAsync(id);
|
|
|
|
|
|
|
|
assert.isTrue(feedItem.isFeedItem);
|
|
|
|
assert.instanceOf(feedItem, Zotero.FeedItem);
|
|
|
|
});
|
|
|
|
});
|
2015-04-25 07:14:53 +00:00
|
|
|
});
|