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
|
|
|
|
|
2017-04-12 04:56:37 +00:00
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2022-02-19 18:41:59 +00:00
|
|
|
|
describe("#copyChildItems()", function () {
|
|
|
|
|
var group;
|
|
|
|
|
|
|
|
|
|
before(async function () {
|
|
|
|
|
group = await createGroup();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
after(async function () {
|
|
|
|
|
await group.eraseTx();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should copy annotations from a group library to a personal library", async function () {
|
|
|
|
|
await Zotero.Users.setCurrentUserID(1);
|
|
|
|
|
await Zotero.Users.setName(1, 'Name 1');
|
|
|
|
|
await Zotero.Users.setName(12345, 'Name 2');
|
|
|
|
|
|
|
|
|
|
var userLibraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
|
|
|
|
|
|
var item = await createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.pdf');
|
|
|
|
|
var attachment = await Zotero.Attachments.importFromFile({
|
|
|
|
|
file,
|
|
|
|
|
parentItemID: item.id
|
|
|
|
|
});
|
|
|
|
|
// Annotation by this user
|
|
|
|
|
var annotation1 = await createAnnotation('highlight', attachment);
|
|
|
|
|
await annotation1.saveTx();
|
|
|
|
|
// Annotation by this user with createdByUserID set
|
|
|
|
|
var annotation2 = await createAnnotation('highlight', attachment);
|
|
|
|
|
annotation2.createdByUserID = 1;
|
|
|
|
|
await annotation2.saveTx({
|
|
|
|
|
skipEditCheck: true
|
|
|
|
|
});
|
|
|
|
|
// Annotation by another user
|
|
|
|
|
var annotation3 = await createAnnotation('highlight', attachment);
|
|
|
|
|
annotation3.createdByUserID = 12345;
|
|
|
|
|
await annotation3.saveTx({
|
|
|
|
|
skipEditCheck: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var newItem = item.clone(userLibraryID);
|
|
|
|
|
await newItem.saveTx();
|
|
|
|
|
var newAttachment = attachment.clone(userLibraryID);
|
|
|
|
|
await newAttachment.saveTx();
|
|
|
|
|
|
|
|
|
|
await Zotero.DB.executeTransaction(async function () {
|
|
|
|
|
await Zotero.Items.copyChildItems(attachment, newAttachment);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check annotations
|
|
|
|
|
var annotations = newAttachment.getAnnotations();
|
|
|
|
|
assert.lengthOf(annotations, 3);
|
|
|
|
|
var newAnnotation1 = annotations.find(o => o.annotationText == annotation1.annotationText);
|
|
|
|
|
var newAnnotation2 = annotations.find(o => o.annotationText == annotation2.annotationText);
|
|
|
|
|
var newAnnotation3 = annotations.find(o => o.annotationText == annotation3.annotationText);
|
|
|
|
|
// Current user's name shouldn't have been transferred
|
|
|
|
|
assert.isNull(newAnnotation1.annotationAuthorName);
|
|
|
|
|
assert.isNull(newAnnotation2.annotationAuthorName);
|
|
|
|
|
// Other user's should've been transferred
|
|
|
|
|
assert.equal(newAnnotation3.annotationAuthorName, 'Name 2');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
})
|
|
|
|
|
|
2018-04-15 21:05:23 +00:00
|
|
|
|
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]);
|
|
|
|
|
})
|
|
|
|
|
|
2019-04-02 18:01:25 +00:00
|
|
|
|
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');
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-29 09:42:22 +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);
|
|
|
|
|
});
|
|
|
|
|
|
2018-04-14 14:26:16 +00:00
|
|
|
|
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);
|
|
|
|
|
})
|
|
|
|
|
|
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
|
|
|
|
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]);
|
|
|
|
|
})
|
|
|
|
|
|
2020-06-06 21:16:09 +00:00
|
|
|
|
// 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
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
|
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]);
|
|
|
|
|
})
|
2022-03-09 22:26:26 +00:00
|
|
|
|
|
|
|
|
|
it("should merge identical attachments based on file hash", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importPDFAttachment(item2);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should merge one attachment per item into the master attachment", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importPDFAttachment(item2);
|
|
|
|
|
|
|
|
|
|
let item3 = item1.clone();
|
|
|
|
|
await item3.saveTx();
|
|
|
|
|
let attachment3 = await importPDFAttachment(item3);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2, item3]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
assert.isTrue(item3.deleted);
|
|
|
|
|
assert.isTrue(attachment3.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should merge identical attachments based on content hash", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importFileAttachment('duplicatesMerge_test_new_md5.pdf', { parentItemID: item2.id });
|
|
|
|
|
|
|
|
|
|
assert.equal(await attachment1.attachmentText, await attachment2.attachmentText);
|
|
|
|
|
assert.notEqual(await attachment1.attachmentHash, await attachment2.attachmentHash);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
});
|
2022-03-13 00:09:23 +00:00
|
|
|
|
|
2022-03-13 01:17:13 +00:00
|
|
|
|
it("should merge identical attachments based on content hash when unindexed", async function () {
|
2022-03-13 00:09:23 +00:00
|
|
|
|
let item1 = await createDataObject('item');
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importFileAttachment('duplicatesMerge_test_new_md5.pdf', { parentItemID: item2.id });
|
|
|
|
|
|
|
|
|
|
await Zotero.DB.executeTransaction(async () => {
|
|
|
|
|
await Zotero.FullText.clearItemWords(attachment1.id);
|
|
|
|
|
await Zotero.FullText.clearItemWords(attachment2.id);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't merge attachments based on content hash when files are empty", async function () {
|
2022-03-09 22:26:26 +00:00
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importFileAttachment('empty.pdf', { parentItemID: item1.id });
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importFileAttachment('duplicatesMerge_empty_new_md5.pdf', { parentItemID: item2.id });
|
|
|
|
|
|
|
|
|
|
assert.equal(await attachment1.attachmentText, await attachment2.attachmentText);
|
|
|
|
|
assert.notEqual(await attachment1.attachmentHash, await attachment2.attachmentHash);
|
|
|
|
|
assert.isEmpty(await attachment1.attachmentText);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 2);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isFalse(attachment2.deleted);
|
|
|
|
|
});
|
2022-03-24 10:39:01 +00:00
|
|
|
|
|
|
|
|
|
it("should ignore attachment with missing file", async function () {
|
|
|
|
|
let item1 = await createDataObject('item');
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importPDFAttachment(item2);
|
|
|
|
|
// Delete the attachment file
|
|
|
|
|
await OS.File.remove(await attachment2.getFilePathAsync());
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 2);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isFalse(attachment2.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
2022-03-09 22:26:26 +00:00
|
|
|
|
it("should allow small differences when hashing content", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importFileAttachment('duplicatesMerge_JSTOR_1.pdf', { parentItemID: item1.id });
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importFileAttachment('duplicatesMerge_JSTOR_2.pdf', { parentItemID: item2.id });
|
|
|
|
|
|
|
|
|
|
assert.notEqual(await attachment1.attachmentText, await attachment2.attachmentText);
|
|
|
|
|
assert.notEqual(await attachment1.attachmentHash, await attachment2.attachmentHash);
|
|
|
|
|
assert.equal(
|
|
|
|
|
(await Zotero.Items._hashAttachmentText(attachment1)).fromText,
|
|
|
|
|
(await Zotero.Items._hashAttachmentText(attachment2)).fromText
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should keep similar but not identical attachments separate", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importFileAttachment('wonderland_short.pdf', { parentItemID: item1.id });
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importFileAttachment('wonderland_long.pdf', { parentItemID: item2.id });
|
|
|
|
|
|
|
|
|
|
assert.notEqual(await attachment1.attachmentText, await attachment2.attachmentText);
|
|
|
|
|
assert.notEqual(await attachment1.attachmentHash, await attachment2.attachmentHash);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 2);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isFalse(attachment2.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should only match attachments one-to-one", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importFileAttachment('wonderland_short_watermarked_1.pdf', { parentItemID: item1.id });
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importFileAttachment('wonderland_short_watermarked_2.pdf', { parentItemID: item2.id });
|
|
|
|
|
let attachment3 = await importFileAttachment('wonderland_short_watermarked_2.pdf', { parentItemID: item2.id });
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 2);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
// Doesn't matter which got merged
|
|
|
|
|
assert.isTrue((attachment2.deleted || attachment3.deleted) && !(attachment2.deleted && attachment3.deleted));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should copy annotations when merging", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
let annotation1 = await createAnnotation('note', attachment1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importPDFAttachment(item2);
|
|
|
|
|
let annotation2 = await createAnnotation('highlight', attachment2);
|
|
|
|
|
let annotation2Note = await Zotero.EditorInstance.createNoteFromAnnotations([annotation2], item2.id);
|
|
|
|
|
|
|
|
|
|
assert.include(annotation2Note.getNote(), attachment2.key);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.isFalse(annotation1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
assert.isFalse(annotation2.deleted);
|
|
|
|
|
assert.equal(annotation1.parentItemID, attachment1.id);
|
|
|
|
|
assert.equal(annotation2.parentItemID, attachment1.id);
|
|
|
|
|
assert.notInclude(annotation2Note.getNote(), item2.key);
|
|
|
|
|
assert.include(annotation2Note.getNote(), item1.key);
|
|
|
|
|
assert.notInclude(annotation2Note.getNote(), attachment2.key);
|
|
|
|
|
assert.include(annotation2Note.getNote(), attachment1.key);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should update all item keys when moving notes", async function () {
|
|
|
|
|
let attachmentFilenames = [
|
|
|
|
|
'recognizePDF_test_arXiv.pdf',
|
|
|
|
|
'recognizePDF_test_DOI.pdf',
|
|
|
|
|
'recognizePDF_test_title.pdf'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachments1 = [];
|
|
|
|
|
for (let filename of attachmentFilenames) {
|
|
|
|
|
let attachment = await importFileAttachment(filename, { parentID: item1.id });
|
|
|
|
|
attachments1.push(attachment);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachments2 = [];
|
|
|
|
|
let annotations2 = [];
|
|
|
|
|
let notes2 = [];
|
|
|
|
|
for (let filename of attachmentFilenames) {
|
|
|
|
|
let attachment = await importFileAttachment(filename, { parentID: item2.id });
|
|
|
|
|
let annotation = await createAnnotation('highlight', attachment);
|
|
|
|
|
let note = await Zotero.EditorInstance.createNoteFromAnnotations([annotation], item2.id);
|
|
|
|
|
attachments2.push(attachment);
|
|
|
|
|
annotations2.push(annotation);
|
|
|
|
|
notes2.push(note);
|
|
|
|
|
|
|
|
|
|
assert.include(note.getNote(), item2.key);
|
|
|
|
|
assert.include(note.getNote(), attachment.key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 3);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
let attachment1 = attachments1[i];
|
|
|
|
|
let attachment2 = attachments2[i];
|
|
|
|
|
let note = notes2[i];
|
|
|
|
|
|
|
|
|
|
assert.equal(note.parentItemID, item1.id);
|
|
|
|
|
assert.include(note.getNote(), item1.key);
|
|
|
|
|
assert.notInclude(note.getNote(), item2.key);
|
|
|
|
|
assert.include(note.getNote(), attachment1.key);
|
|
|
|
|
assert.notInclude(note.getNote(), attachment2.key);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should merge snapshots with the same title, even if URL differs", async function () {
|
|
|
|
|
let content = getTestDataDirectory();
|
|
|
|
|
content.append('snapshot');
|
|
|
|
|
content.append('index.html');
|
|
|
|
|
|
|
|
|
|
let snapshotContent = await Zotero.File.getContentsAsync(content);
|
|
|
|
|
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await Zotero.Attachments.importFromSnapshotContent({
|
|
|
|
|
parentItemID: item1.id,
|
|
|
|
|
url: 'https://example.com/test.html',
|
|
|
|
|
title: 'Snapshot',
|
|
|
|
|
snapshotContent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await Zotero.Attachments.importFromSnapshotContent({
|
|
|
|
|
parentItemID: item2.id,
|
|
|
|
|
url: 'https://otherdomain.example.com/test.html',
|
|
|
|
|
title: 'Snapshot',
|
|
|
|
|
snapshotContent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should merge linked URLs", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await Zotero.Attachments.linkFromURL({
|
|
|
|
|
url: 'https://example.com/',
|
|
|
|
|
title: 'Catalog Entry',
|
|
|
|
|
parentItemID: item1.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await Zotero.Attachments.linkFromURL({
|
|
|
|
|
url: 'https://example.com/',
|
|
|
|
|
title: 'Catalog Entry',
|
|
|
|
|
parentItemID: item2.id
|
|
|
|
|
});
|
|
|
|
|
let attachment3 = await Zotero.Attachments.linkFromURL({
|
|
|
|
|
url: 'https://example.com/',
|
|
|
|
|
title: 'Catalog Entry',
|
|
|
|
|
parentItemID: item2.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(attachment1.getField('url'), 'https://example.com/');
|
|
|
|
|
assert.equal(item1.numAttachments(true), 2);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
assert.equal(attachment3.parentItemID, item1.id);
|
|
|
|
|
assert.isFalse(attachment3.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should keep web attachment with same URL but different title", async function () {
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await Zotero.Attachments.linkFromURL({
|
|
|
|
|
url: 'https://example.com/',
|
|
|
|
|
title: 'Catalog Entry',
|
|
|
|
|
parentItemID: item1.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await Zotero.Attachments.linkFromURL({
|
|
|
|
|
url: 'https://example.com/',
|
|
|
|
|
title: 'Official Website',
|
|
|
|
|
parentItemID: item2.id
|
|
|
|
|
});
|
|
|
|
|
let attachment3 = await Zotero.Attachments.linkFromURL({
|
|
|
|
|
url: 'https://example.com/',
|
|
|
|
|
title: 'Catalog Entry',
|
|
|
|
|
parentItemID: item2.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(attachment1.getField('url'), 'https://example.com/');
|
|
|
|
|
assert.equal(item1.numAttachments(true), 2);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.equal(attachment2.parentItemID, item1.id);
|
|
|
|
|
assert.isFalse(attachment2.deleted);
|
|
|
|
|
assert.isTrue(attachment3.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should move related items of merged attachments", async function () {
|
|
|
|
|
let relatedItem = await createDataObject('item');
|
|
|
|
|
|
|
|
|
|
let item1 = await createDataObject('item', { setTitle: true });
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = item1.clone();
|
|
|
|
|
await item2.saveTx();
|
|
|
|
|
let attachment2 = await importPDFAttachment(item2);
|
|
|
|
|
attachment2.addRelatedItem(relatedItem);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item1.deleted);
|
|
|
|
|
assert.isFalse(attachment1.deleted);
|
|
|
|
|
assert.equal(item1.numAttachments(true), 1);
|
|
|
|
|
assert.isTrue(item2.deleted);
|
|
|
|
|
assert.isTrue(attachment2.deleted);
|
|
|
|
|
assert.lengthOf(attachment1.relatedItems, 1);
|
|
|
|
|
assert.equal(attachment1.relatedItems[0], relatedItem.key);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should move merge-tracking relation from replaced attachment to master attachment", async function () {
|
|
|
|
|
let item1 = await createDataObject('item');
|
|
|
|
|
let attachment1 = await importPDFAttachment(item1);
|
|
|
|
|
|
|
|
|
|
let item2 = await createDataObject('item');
|
|
|
|
|
let attachment2 = await importPDFAttachment(item2);
|
|
|
|
|
let attachment2URI = Zotero.URI.getItemURI(attachment2);
|
|
|
|
|
|
|
|
|
|
let item3 = await createDataObject('item');
|
|
|
|
|
let attachment3 = await importPDFAttachment(item3);
|
|
|
|
|
let attachment3URI = Zotero.URI.getItemURI(attachment3);
|
|
|
|
|
|
|
|
|
|
await Zotero.Items.merge(item2, [item3]);
|
|
|
|
|
await Zotero.Items.merge(item1, [item2]);
|
|
|
|
|
|
|
|
|
|
var rels = attachment1.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate);
|
|
|
|
|
assert.lengthOf(rels, 2);
|
|
|
|
|
assert.sameMembers(rels, [attachment2URI, attachment3URI]);
|
|
|
|
|
});
|
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
|
|
|
|
})
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
2018-02-03 09:09:55 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
2020-12-28 00:49:50 +00:00
|
|
|
|
|
|
|
|
|
describe("#_loadChildItems()", function () {
|
|
|
|
|
it("should mark child items as loaded for an attachment", async function () {
|
|
|
|
|
var attachment = await importPDFAttachment();
|
|
|
|
|
var itemID = attachment.id;
|
|
|
|
|
Zotero.Items.unload([itemID]);
|
|
|
|
|
attachment = await Zotero.Items.getAsync(itemID);
|
|
|
|
|
await attachment.loadDataType('childItems');
|
|
|
|
|
assert.isTrue(attachment._loaded.childItems);
|
|
|
|
|
attachment.getAnnotations();
|
2021-03-03 11:23:54 +00:00
|
|
|
|
await attachment.eraseTx();
|
2020-12-28 00:49:50 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
2022-03-22 00:06:27 +00:00
|
|
|
|
|
|
|
|
|
describe("#getSortTitle()", function () {
|
|
|
|
|
it("should strip recognized markup tags", function () {
|
|
|
|
|
let tests = [
|
|
|
|
|
['A title <i>in italics</i>', 'A title in italics'],
|
|
|
|
|
['An unmatched </b> tag', 'An unmatched tag'],
|
|
|
|
|
['A <sup>title</sub> with mismatched tags', 'A title with mismatched tags'],
|
|
|
|
|
['A title with a valid <span style="font-variant:small-caps;">span</span>', 'A title with a valid span'],
|
|
|
|
|
['Another title with a valid <span class="nocase">span</span>', 'Another title with a valid span'],
|
|
|
|
|
['A random <span>span tag</span>', 'A random <span>span tag']
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let [input, expected] of tests) {
|
|
|
|
|
assert.equal(Zotero.Items.getSortTitle(input), expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should strip any punctuation at the beginning of the string", function () {
|
|
|
|
|
let tests = [
|
|
|
|
|
['_title', 'title'],
|
|
|
|
|
['-title', 'title'],
|
|
|
|
|
['-- longer title', 'longer title'],
|
|
|
|
|
['"Quoted title', 'Quoted title']
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let [input, expected] of tests) {
|
|
|
|
|
assert.equal(Zotero.Items.getSortTitle(input), expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should strip quotes", function () {
|
|
|
|
|
let tests = [
|
|
|
|
|
['A "title"', 'A title'],
|
|
|
|
|
['A “title”', 'A title'],
|
|
|
|
|
[' xyz ”””', 'xyz'],
|
|
|
|
|
['‘Punctuation’', 'Punctuation']
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let [input, expected] of tests) {
|
|
|
|
|
assert.equal(Zotero.Items.getSortTitle(input), expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should not strip dashes in the middle of the string", function () {
|
|
|
|
|
let tests = [
|
|
|
|
|
['123-456', '123-456'],
|
|
|
|
|
['Meyers-Briggs', 'Meyers-Briggs'],
|
|
|
|
|
['En–dash', 'En–dash'],
|
|
|
|
|
['Em—dash', 'Em—dash']
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let [input, expected] of tests) {
|
|
|
|
|
assert.equal(Zotero.Items.getSortTitle(input), expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-03-30 18:30:24 +00:00
|
|
|
|
|
|
|
|
|
it("should not strip any punctuation before a digit", function () {
|
|
|
|
|
let tests = [
|
|
|
|
|
['1.5', '1.5'],
|
|
|
|
|
['abc .5', 'abc .5'],
|
|
|
|
|
['abc 5.', 'abc 5']
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let [input, expected] of tests) {
|
|
|
|
|
assert.equal(Zotero.Items.getSortTitle(input), expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-03-22 00:06:27 +00:00
|
|
|
|
});
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|