2015-05-05 06:35:04 +00:00
|
|
|
"use strict";
|
|
|
|
|
2015-04-30 21:06:38 +00:00
|
|
|
describe("Zotero.CollectionTreeView", function() {
|
2016-03-11 12:36:36 +00:00
|
|
|
var win, zp, cv, userLibraryID;
|
2015-04-30 21:06:38 +00:00
|
|
|
|
|
|
|
before(function* () {
|
|
|
|
win = yield loadZoteroPane();
|
2015-06-09 05:29:26 +00:00
|
|
|
zp = win.ZoteroPane;
|
|
|
|
cv = zp.collectionsView;
|
2016-03-11 12:36:36 +00:00
|
|
|
userLibraryID = Zotero.Libraries.userLibraryID;
|
2015-04-30 21:06:38 +00:00
|
|
|
});
|
2015-05-22 23:15:21 +00:00
|
|
|
beforeEach(function () {
|
2015-05-25 02:57:46 +00:00
|
|
|
// TODO: Add a selectCollection() function and select a collection instead?
|
|
|
|
return selectLibrary(win);
|
2015-05-22 23:15:21 +00:00
|
|
|
})
|
2015-04-30 21:06:38 +00:00
|
|
|
after(function () {
|
2015-05-04 06:00:52 +00:00
|
|
|
win.close();
|
2015-04-30 21:06:38 +00:00
|
|
|
});
|
|
|
|
|
2016-03-11 12:34:57 +00:00
|
|
|
describe("#refresh()", function () {
|
|
|
|
it("should show Duplicate Items and Unfiled Items in My Library by default", function* () {
|
|
|
|
Zotero.Prefs.clear('duplicateLibraries');
|
|
|
|
Zotero.Prefs.clear('unfiledLibraries');
|
|
|
|
yield cv.refresh();
|
2016-03-14 00:31:15 +00:00
|
|
|
assert.ok(cv.getRowIndexByID("D" + userLibraryID));
|
|
|
|
assert.ok(cv.getRowIndexByID("U" + userLibraryID));
|
2016-03-11 12:34:57 +00:00
|
|
|
assert.equal(Zotero.Prefs.get('duplicateLibraries'), "" + userLibraryID);
|
|
|
|
assert.equal(Zotero.Prefs.get('unfiledLibraries'), "" + userLibraryID);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't show Duplicate Items and Unfiled Items if hidden", function* () {
|
|
|
|
Zotero.Prefs.set('duplicateLibraries', "");
|
|
|
|
Zotero.Prefs.set('unfiledLibraries', "");
|
|
|
|
yield cv.refresh();
|
2016-03-12 10:03:47 +00:00
|
|
|
assert.isFalse(cv.getRowIndexByID("D" + userLibraryID));
|
|
|
|
assert.isFalse(cv.getRowIndexByID("U" + userLibraryID));
|
2016-03-11 12:34:57 +00:00
|
|
|
assert.strictEqual(Zotero.Prefs.get('duplicateLibraries'), "");
|
|
|
|
assert.strictEqual(Zotero.Prefs.get('unfiledLibraries'), "");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-05-22 18:40:04 +00:00
|
|
|
describe("collapse/expand", function () {
|
|
|
|
it("should close and open My Library repeatedly", function* () {
|
2016-03-11 12:36:36 +00:00
|
|
|
yield cv.selectLibrary(userLibraryID);
|
2015-05-22 18:40:04 +00:00
|
|
|
var row = cv.selection.currentIndex;
|
|
|
|
|
2016-03-11 12:36:36 +00:00
|
|
|
cv.collapseLibrary(userLibraryID);
|
2015-05-22 18:40:04 +00:00
|
|
|
var nextRow = cv.getRow(row + 1);
|
|
|
|
assert.equal(cv.selection.currentIndex, row);
|
|
|
|
assert.ok(nextRow.isSeparator());
|
|
|
|
assert.isFalse(cv.isContainerOpen(row));
|
|
|
|
|
2016-03-11 12:36:36 +00:00
|
|
|
yield cv.expandLibrary(userLibraryID);
|
2015-05-22 18:40:04 +00:00
|
|
|
nextRow = cv.getRow(row + 1);
|
|
|
|
assert.equal(cv.selection.currentIndex, row);
|
|
|
|
assert.ok(!nextRow.isSeparator());
|
|
|
|
assert.ok(cv.isContainerOpen(row));
|
|
|
|
|
2016-03-11 12:36:36 +00:00
|
|
|
cv.collapseLibrary(userLibraryID);
|
2015-05-22 18:40:04 +00:00
|
|
|
nextRow = cv.getRow(row + 1);
|
|
|
|
assert.equal(cv.selection.currentIndex, row);
|
|
|
|
assert.ok(nextRow.isSeparator());
|
|
|
|
assert.isFalse(cv.isContainerOpen(row));
|
|
|
|
|
2016-03-11 12:36:36 +00:00
|
|
|
yield cv.expandLibrary(userLibraryID);
|
2015-05-22 18:40:04 +00:00
|
|
|
nextRow = cv.getRow(row + 1);
|
|
|
|
assert.equal(cv.selection.currentIndex, row);
|
|
|
|
assert.ok(!nextRow.isSeparator());
|
|
|
|
assert.ok(cv.isContainerOpen(row));
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2016-05-07 08:02:42 +00:00
|
|
|
describe("#expandLibrary()", function () {
|
|
|
|
var libraryRow, col1, col2, col3;
|
|
|
|
|
|
|
|
before(function* () {
|
|
|
|
yield cv.selectLibrary(userLibraryID);
|
|
|
|
libraryRow = cv.selection.currentIndex;
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(function* () {
|
|
|
|
// My Library
|
|
|
|
// - A
|
|
|
|
// - B
|
|
|
|
// - C
|
|
|
|
col1 = yield createDataObject('collection');
|
|
|
|
col2 = yield createDataObject('collection', { parentID: col1.id });
|
|
|
|
col3 = yield createDataObject('collection', { parentID: col2.id });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should open a library and respect stored container state", function* () {
|
|
|
|
// Collapse B
|
|
|
|
yield cv.toggleOpenState(cv.getRowIndexByID(col2.collectionTreeViewID));
|
|
|
|
yield cv._rememberOpenStates();
|
|
|
|
|
|
|
|
// Close and reopen library
|
|
|
|
yield cv.toggleOpenState(libraryRow);
|
|
|
|
yield cv.expandLibrary(userLibraryID);
|
|
|
|
|
|
|
|
assert.ok(cv.getRowIndexByID(col1.collectionTreeViewID))
|
|
|
|
assert.ok(cv.getRowIndexByID(col2.collectionTreeViewID))
|
|
|
|
assert.isFalse(cv.getRowIndexByID(col3.collectionTreeViewID))
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should open a library and all subcollections in recursive mode", function* () {
|
|
|
|
yield cv.toggleOpenState(cv.getRowIndexByID(col2.collectionTreeViewID));
|
|
|
|
yield cv._rememberOpenStates();
|
|
|
|
|
|
|
|
// Close and reopen library
|
|
|
|
yield cv.toggleOpenState(libraryRow);
|
|
|
|
yield cv.expandLibrary(userLibraryID, true);
|
|
|
|
|
|
|
|
assert.ok(cv.getRowIndexByID(col1.collectionTreeViewID))
|
|
|
|
assert.ok(cv.getRowIndexByID(col2.collectionTreeViewID))
|
|
|
|
assert.ok(cv.getRowIndexByID(col3.collectionTreeViewID))
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-06-01 19:31:57 +00:00
|
|
|
describe("#expandToCollection()", function () {
|
|
|
|
it("should expand a collection to a subcollection", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection');
|
|
|
|
var collection2 = createUnsavedDataObject('collection');
|
|
|
|
collection2.parentID = collection1.id;
|
2015-06-02 07:33:05 +00:00
|
|
|
yield collection2.saveTx({
|
2015-06-01 19:31:57 +00:00
|
|
|
skipSelect: true
|
|
|
|
});
|
|
|
|
var row = cv.getRowIndexByID("C" + collection1.id);
|
|
|
|
assert.isFalse(cv.isContainerOpen(row));
|
|
|
|
|
|
|
|
yield cv.expandToCollection(collection2.id);
|
|
|
|
|
|
|
|
// Make sure parent row position hasn't changed
|
|
|
|
assert.equal(cv.getRowIndexByID("C" + collection1.id), row);
|
|
|
|
// Parent should have been opened
|
|
|
|
assert.isTrue(cv.isContainerOpen(row));
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-05-25 05:43:07 +00:00
|
|
|
describe("#selectByID()", function () {
|
2015-05-25 02:04:40 +00:00
|
|
|
it("should select the trash", function* () {
|
2015-06-08 08:12:45 +00:00
|
|
|
yield cv.selectByID("T1");
|
|
|
|
var row = cv.selection.currentIndex;
|
|
|
|
var treeRow = cv.getRow(row);
|
2015-05-25 02:04:40 +00:00
|
|
|
assert.ok(treeRow.isTrash());
|
2016-03-11 12:36:36 +00:00
|
|
|
assert.equal(treeRow.ref.libraryID, userLibraryID);
|
2015-05-25 02:04:40 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-05-25 05:43:07 +00:00
|
|
|
describe("#selectWait()", function () {
|
|
|
|
it("shouldn't hang if row is already selected", function* () {
|
2016-03-11 12:36:36 +00:00
|
|
|
var row = cv.getRowIndexByID("T" + userLibraryID);
|
2015-06-08 08:12:45 +00:00
|
|
|
cv.selection.select(row);
|
2015-05-25 05:43:07 +00:00
|
|
|
yield Zotero.Promise.delay(50);
|
2015-06-08 08:12:45 +00:00
|
|
|
yield cv.selectWait(row);
|
2015-05-25 05:43:07 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2015-04-30 21:06:38 +00:00
|
|
|
describe("#notify()", function () {
|
|
|
|
it("should select a new collection", function* () {
|
|
|
|
// Create collection
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = "Select new collection";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx();
|
2015-04-30 21:06:38 +00:00
|
|
|
|
|
|
|
// New collection should be selected
|
2015-06-08 08:12:45 +00:00
|
|
|
var selected = cv.getSelectedCollection(true);
|
2015-04-30 21:06:38 +00:00
|
|
|
assert.equal(selected, id);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't select a new collection if skipNotifier is passed", function* () {
|
|
|
|
// Create collection with skipNotifier flag
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = "No select on skipNotifier";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx({
|
2015-04-30 21:06:38 +00:00
|
|
|
skipNotifier: true
|
|
|
|
});
|
|
|
|
|
|
|
|
// Library should still be selected
|
2016-03-11 12:36:36 +00:00
|
|
|
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
|
2015-04-30 21:06:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't select a new collection if skipSelect is passed", function* () {
|
|
|
|
// Create collection with skipSelect flag
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = "No select on skipSelect";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx({
|
2015-04-30 21:06:38 +00:00
|
|
|
skipSelect: true
|
|
|
|
});
|
|
|
|
|
|
|
|
// Library should still be selected
|
2016-03-11 12:36:36 +00:00
|
|
|
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
|
2015-04-30 21:06:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't select a modified collection", function* () {
|
|
|
|
// Create collection
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = "No select on modify";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx();
|
2015-04-30 21:06:38 +00:00
|
|
|
|
2015-05-25 02:57:46 +00:00
|
|
|
yield selectLibrary(win);
|
2015-04-30 21:06:38 +00:00
|
|
|
|
|
|
|
collection.name = "No select on modify 2";
|
2015-05-10 08:20:47 +00:00
|
|
|
yield collection.saveTx();
|
2015-04-30 21:06:38 +00:00
|
|
|
|
|
|
|
// Modified collection should not be selected
|
2016-03-11 12:36:36 +00:00
|
|
|
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
|
2015-04-30 21:06:38 +00:00
|
|
|
});
|
|
|
|
|
2015-06-09 05:29:26 +00:00
|
|
|
it("should maintain selection on a selected modified collection", function* () {
|
2015-04-30 21:06:38 +00:00
|
|
|
// Create collection
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = "Reselect on modify";
|
2015-05-10 08:20:47 +00:00
|
|
|
var id = yield collection.saveTx();
|
2015-04-30 21:06:38 +00:00
|
|
|
|
2015-06-08 08:12:45 +00:00
|
|
|
var selected = cv.getSelectedCollection(true);
|
2015-04-30 21:06:38 +00:00
|
|
|
assert.equal(selected, id);
|
|
|
|
|
|
|
|
collection.name = "Reselect on modify 2";
|
2015-05-10 08:20:47 +00:00
|
|
|
yield collection.saveTx();
|
2015-04-30 21:06:38 +00:00
|
|
|
|
|
|
|
// Modified collection should still be selected
|
2015-06-08 08:12:45 +00:00
|
|
|
selected = cv.getSelectedCollection(true);
|
2015-04-30 21:06:38 +00:00
|
|
|
assert.equal(selected, id);
|
|
|
|
});
|
2015-05-07 22:18:48 +00:00
|
|
|
|
2016-03-25 20:48:33 +00:00
|
|
|
it("should update the editability of the current view", function* () {
|
|
|
|
var group = yield createGroup({
|
|
|
|
editable: false,
|
|
|
|
filesEditable: false
|
|
|
|
});
|
|
|
|
yield cv.selectLibrary(group.libraryID);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
assert.isFalse(cv.selectedTreeRow.editable);
|
|
|
|
var cmd = win.document.getElementById('cmd_zotero_newStandaloneNote');
|
|
|
|
assert.isTrue(cmd.getAttribute('disabled') == 'true');
|
|
|
|
|
|
|
|
group.editable = true;
|
|
|
|
yield group.saveTx();
|
|
|
|
|
|
|
|
assert.isTrue(cv.selectedTreeRow.editable);
|
|
|
|
assert.isFalse(cmd.getAttribute('disabled') == 'true');
|
|
|
|
});
|
|
|
|
|
2015-06-09 05:29:26 +00:00
|
|
|
it("should re-sort a modified collection", function* () {
|
|
|
|
var prefix = Zotero.Utilities.randomString() + " ";
|
|
|
|
var collectionA = yield createDataObject('collection', { name: prefix + "A" });
|
|
|
|
var collectionB = yield createDataObject('collection', { name: prefix + "B" });
|
|
|
|
|
|
|
|
var aRow = cv.getRowIndexByID("C" + collectionA.id);
|
|
|
|
var aRowOriginal = aRow;
|
|
|
|
var bRow = cv.getRowIndexByID("C" + collectionB.id);
|
|
|
|
assert.equal(bRow, aRow + 1);
|
|
|
|
|
|
|
|
collectionA.name = prefix + "C";
|
|
|
|
yield collectionA.saveTx();
|
|
|
|
|
|
|
|
var aRow = cv.getRowIndexByID("C" + collectionA.id);
|
|
|
|
var bRow = cv.getRowIndexByID("C" + collectionB.id);
|
|
|
|
assert.equal(bRow, aRowOriginal);
|
|
|
|
assert.equal(aRow, bRow + 1);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should re-sort a modified search", function* () {
|
|
|
|
var prefix = Zotero.Utilities.randomString() + " ";
|
|
|
|
var searchA = yield createDataObject('search', { name: prefix + "A" });
|
|
|
|
var searchB = yield createDataObject('search', { name: prefix + "B" });
|
|
|
|
|
|
|
|
var aRow = cv.getRowIndexByID("S" + searchA.id);
|
|
|
|
var aRowOriginal = aRow;
|
|
|
|
var bRow = cv.getRowIndexByID("S" + searchB.id);
|
|
|
|
assert.equal(bRow, aRow + 1);
|
|
|
|
|
|
|
|
searchA.name = prefix + "C";
|
|
|
|
yield searchA.saveTx();
|
|
|
|
|
|
|
|
var aRow = cv.getRowIndexByID("S" + searchA.id);
|
|
|
|
var bRow = cv.getRowIndexByID("S" + searchB.id);
|
|
|
|
assert.equal(bRow, aRowOriginal);
|
|
|
|
assert.equal(aRow, bRow + 1);
|
|
|
|
})
|
|
|
|
|
2016-02-21 10:19:01 +00:00
|
|
|
|
|
|
|
it("should add multiple collections", function* () {
|
|
|
|
var col1, col2;
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
|
|
col1 = createUnsavedDataObject('collection');
|
|
|
|
col2 = createUnsavedDataObject('collection');
|
|
|
|
yield col1.save();
|
|
|
|
yield col2.save();
|
|
|
|
});
|
|
|
|
|
|
|
|
var aRow = cv.getRowIndexByID("C" + col1.id);
|
|
|
|
var bRow = cv.getRowIndexByID("C" + col2.id);
|
|
|
|
assert.isAbove(aRow, 0);
|
|
|
|
assert.isAbove(bRow, 0);
|
|
|
|
// skipSelect is implied for multiple collections, so library should still be selected
|
|
|
|
assert.equal(cv.selection.currentIndex, 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2015-06-09 05:29:26 +00:00
|
|
|
it("shouldn't refresh the items list when a collection is modified", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
var itemsView = zp.itemsView;
|
|
|
|
|
|
|
|
collection.name = "New Name";
|
|
|
|
yield collection.saveTx();
|
|
|
|
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
assert.equal(zp.itemsView, itemsView);
|
|
|
|
})
|
|
|
|
|
2015-05-07 22:18:48 +00:00
|
|
|
it("should add a saved search after collections", function* () {
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
collection.name = "Test";
|
2015-05-10 08:20:47 +00:00
|
|
|
var collectionID = yield collection.saveTx();
|
2015-05-07 22:18:48 +00:00
|
|
|
|
|
|
|
var search = new Zotero.Search;
|
|
|
|
search.name = "A Test Search";
|
|
|
|
search.addCondition('title', 'contains', 'test');
|
2015-05-10 08:20:47 +00:00
|
|
|
var searchID = yield search.saveTx();
|
2015-05-07 22:18:48 +00:00
|
|
|
|
|
|
|
var collectionRow = cv._rowMap["C" + collectionID];
|
|
|
|
var searchRow = cv._rowMap["S" + searchID];
|
2016-03-11 12:36:36 +00:00
|
|
|
var duplicatesRow = cv._rowMap["D" + userLibraryID];
|
|
|
|
var unfiledRow = cv._rowMap["U" + userLibraryID];
|
2015-05-07 22:18:48 +00:00
|
|
|
|
|
|
|
assert.isAbove(searchRow, collectionRow);
|
|
|
|
// If there's a duplicates row or an unfiled row, add before those.
|
|
|
|
// Otherwise, add before the trash
|
|
|
|
if (duplicatesRow !== undefined) {
|
|
|
|
assert.isBelow(searchRow, duplicatesRow);
|
|
|
|
}
|
|
|
|
else if (unfiledRow !== undefined) {
|
|
|
|
assert.isBelow(searchRow, unfiledRow);
|
|
|
|
}
|
|
|
|
else {
|
2016-03-11 12:36:36 +00:00
|
|
|
var trashRow = cv._rowMap["T" + userLibraryID];
|
2015-05-07 22:18:48 +00:00
|
|
|
assert.isBelow(searchRow, trashRow);
|
|
|
|
}
|
|
|
|
})
|
2015-06-08 08:04:38 +00:00
|
|
|
|
2015-07-19 21:08:36 +00:00
|
|
|
it("shouldn't select a new group", function* () {
|
|
|
|
var group = yield createGroup();
|
|
|
|
// Library should still be selected
|
2016-03-11 12:36:36 +00:00
|
|
|
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
|
2015-07-19 21:08:36 +00:00
|
|
|
})
|
|
|
|
|
2015-06-08 08:04:38 +00:00
|
|
|
it("should remove a group and all children", function* () {
|
|
|
|
// Make sure Group Libraries separator and header exist already,
|
|
|
|
// since otherwise they'll interfere with the count
|
|
|
|
yield getGroup();
|
|
|
|
|
|
|
|
var originalRowCount = cv.rowCount;
|
|
|
|
|
|
|
|
var group = yield createGroup();
|
|
|
|
yield createDataObject('collection', { libraryID: group.libraryID });
|
|
|
|
var c = yield createDataObject('collection', { libraryID: group.libraryID });
|
|
|
|
yield createDataObject('collection', { libraryID: group.libraryID, parentID: c.id });
|
|
|
|
yield createDataObject('collection', { libraryID: group.libraryID });
|
|
|
|
yield createDataObject('collection', { libraryID: group.libraryID });
|
|
|
|
|
|
|
|
// Group, collections, and trash
|
|
|
|
assert.equal(cv.rowCount, originalRowCount + 7);
|
|
|
|
|
|
|
|
var spy = sinon.spy(cv, "refresh");
|
|
|
|
try {
|
|
|
|
yield group.eraseTx();
|
|
|
|
|
|
|
|
assert.equal(cv.rowCount, originalRowCount);
|
|
|
|
// Make sure the tree wasn't refreshed
|
|
|
|
sinon.assert.notCalled(spy);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
spy.restore();
|
|
|
|
}
|
|
|
|
})
|
2016-01-13 13:13:29 +00:00
|
|
|
|
|
|
|
it("should select a new feed", function* () {
|
|
|
|
var feed = yield createFeed();
|
|
|
|
// Library should still be selected
|
|
|
|
assert.equal(cv.getSelectedLibraryID(), feed.id);
|
|
|
|
})
|
|
|
|
|
2015-04-30 21:06:38 +00:00
|
|
|
})
|
2015-05-22 23:15:21 +00:00
|
|
|
|
|
|
|
describe("#drop()", function () {
|
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
|
|
|
/**
|
|
|
|
* Simulate a drag and drop
|
|
|
|
*
|
2015-08-08 21:26:42 +00:00
|
|
|
* @param {String} type - 'item' or 'collection'
|
|
|
|
* @param {String|Object} targetRow - Tree row id (e.g., "L123"), or { row, orient }
|
|
|
|
* @param {Integer[]} collectionIDs
|
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
|
|
|
* @param {Promise} [promise] - If a promise is provided, it will be waited for and its
|
2015-08-08 21:26:42 +00:00
|
|
|
* value returned after the drag. Otherwise, an 'add' event will be waited for, and
|
|
|
|
* an object with 'ids' and 'extraData' will be returned.
|
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
|
|
|
*/
|
2015-08-08 21:26:42 +00:00
|
|
|
var drop = Zotero.Promise.coroutine(function* (objectType, targetRow, ids, promise) {
|
|
|
|
if (typeof targetRow == 'string') {
|
|
|
|
var row = cv.getRowIndexByID(targetRow);
|
|
|
|
var orient = 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var { row, orient } = targetRow;
|
|
|
|
}
|
2015-05-22 23:15:21 +00:00
|
|
|
|
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 stub = sinon.stub(Zotero.DragDrop, "getDragTarget");
|
2015-06-08 08:12:45 +00:00
|
|
|
stub.returns(cv.getRow(row));
|
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
|
|
|
if (!promise) {
|
2015-08-08 21:26:42 +00:00
|
|
|
promise = waitForNotifierEvent("add", objectType);
|
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
|
|
|
}
|
2015-08-08 21:26:42 +00:00
|
|
|
yield cv.drop(row, orient, {
|
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
|
|
|
dropEffect: 'copy',
|
|
|
|
effectAllowed: 'copy',
|
2015-08-08 21:26:42 +00:00
|
|
|
mozSourceNode: win.document.getElementById(`zotero-${objectType}s-tree`),
|
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
|
|
|
types: {
|
|
|
|
contains: function (type) {
|
2015-08-08 21:26:42 +00:00
|
|
|
return type == `zotero/${objectType}`;
|
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
|
|
|
}
|
|
|
|
},
|
|
|
|
getData: function (type) {
|
2015-08-08 21:26:42 +00:00
|
|
|
if (type == `zotero/${objectType}`) {
|
|
|
|
return ids.join(",");
|
2015-05-22 23:15:21 +00:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
});
|
|
|
|
|
|
|
|
// Add observer to wait for add
|
|
|
|
var result = yield promise;
|
|
|
|
stub.restore();
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2015-08-08 21:26:42 +00:00
|
|
|
var canDrop = Zotero.Promise.coroutine(function* (type, targetRowID, ids) {
|
2015-06-08 08:12:45 +00:00
|
|
|
var row = cv.getRowIndexByID(targetRowID);
|
2015-05-22 23:15:21 +00:00
|
|
|
|
|
|
|
var stub = sinon.stub(Zotero.DragDrop, "getDragTarget");
|
2015-06-08 08:12:45 +00:00
|
|
|
stub.returns(cv.getRow(row));
|
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 dt = {
|
2015-05-22 23:15:21 +00:00
|
|
|
dropEffect: 'copy',
|
|
|
|
effectAllowed: 'copy',
|
2015-08-08 21:26:42 +00:00
|
|
|
mozSourceNode: win.document.getElementById(`zotero-${type}s-tree`),
|
2015-05-22 23:15:21 +00:00
|
|
|
types: {
|
|
|
|
contains: function (type) {
|
2015-08-08 21:26:42 +00:00
|
|
|
return type == `zotero/${type}`;
|
2015-05-22 23:15:21 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
getData: function (type) {
|
2015-08-08 21:26:42 +00:00
|
|
|
if (type == `zotero/${type}`) {
|
|
|
|
return ids.join(",");
|
2015-05-22 23:15:21 +00:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
};
|
2015-06-08 08:12:45 +00:00
|
|
|
var canDrop = cv.canDropCheck(row, 0, dt);
|
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
|
|
|
if (canDrop) {
|
2015-06-08 08:12:45 +00:00
|
|
|
canDrop = yield cv.canDropCheckAsync(row, 0, dt);
|
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
|
|
|
}
|
2015-05-22 23:15:21 +00:00
|
|
|
stub.restore();
|
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
|
|
|
return canDrop;
|
|
|
|
});
|
|
|
|
|
2015-08-08 21:26:42 +00:00
|
|
|
describe("with items", function () {
|
|
|
|
it("should add an item to a collection", function* () {
|
|
|
|
var collection = yield createDataObject('collection', false, { skipSelect: true });
|
|
|
|
var item = yield createDataObject('item', false, { skipSelect: true });
|
|
|
|
|
|
|
|
// Add observer to wait for collection add
|
|
|
|
var deferred = Zotero.Promise.defer();
|
|
|
|
var observerID = Zotero.Notifier.registerObserver({
|
|
|
|
notify: function (event, type, ids, extraData) {
|
|
|
|
if (type == 'collection-item' && event == 'add'
|
|
|
|
&& ids[0] == collection.id + "-" + item.id) {
|
|
|
|
setTimeout(function () {
|
|
|
|
deferred.resolve();
|
|
|
|
});
|
|
|
|
}
|
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
|
|
|
}
|
2015-08-08 21:26:42 +00:00
|
|
|
}, 'collection-item', 'test');
|
|
|
|
|
|
|
|
yield drop('item', 'C' + collection.id, [item.id], deferred.promise);
|
|
|
|
|
|
|
|
Zotero.Notifier.unregisterObserver(observerID);
|
|
|
|
|
|
|
|
yield cv.selectCollection(collection.id);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var itemsView = win.ZoteroPane.itemsView
|
|
|
|
assert.equal(itemsView.rowCount, 1);
|
|
|
|
var treeRow = itemsView.getRow(0);
|
|
|
|
assert.equal(treeRow.ref.id, item.id);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should copy an item with an attachment to a group", function* () {
|
|
|
|
var group = yield createGroup();
|
|
|
|
|
|
|
|
var item = yield createDataObject('item', false, { skipSelect: true });
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
file.append('test.png');
|
|
|
|
var attachment = yield Zotero.Attachments.importFromFile({
|
|
|
|
file: file,
|
|
|
|
parentItemID: item.id
|
|
|
|
});
|
|
|
|
|
|
|
|
var ids = (yield drop('item', 'L' + group.libraryID, [item.id])).ids;
|
|
|
|
|
|
|
|
yield cv.selectLibrary(group.libraryID);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
// Check parent
|
|
|
|
var itemsView = win.ZoteroPane.itemsView;
|
|
|
|
assert.equal(itemsView.rowCount, 1);
|
|
|
|
var treeRow = itemsView.getRow(0);
|
|
|
|
assert.equal(treeRow.ref.libraryID, group.libraryID);
|
|
|
|
assert.equal(treeRow.ref.id, ids[0]);
|
|
|
|
// New item should link back to original
|
2016-03-18 08:04:33 +00:00
|
|
|
var linked = item.getLinkedItem(group.libraryID);
|
2015-08-08 21:26:42 +00:00
|
|
|
assert.equal(linked.id, treeRow.ref.id);
|
|
|
|
|
|
|
|
// Check attachment
|
|
|
|
assert.isTrue(itemsView.isContainer(0));
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
itemsView.toggleOpenState(0);
|
2015-08-08 21:26:42 +00:00
|
|
|
assert.equal(itemsView.rowCount, 2);
|
|
|
|
treeRow = itemsView.getRow(1);
|
|
|
|
assert.equal(treeRow.ref.id, ids[1]);
|
|
|
|
// New attachment should link back to original
|
2016-03-18 08:04:33 +00:00
|
|
|
linked = attachment.getLinkedItem(group.libraryID);
|
2015-08-08 21:26:42 +00:00
|
|
|
assert.equal(linked.id, treeRow.ref.id);
|
|
|
|
|
|
|
|
return group.eraseTx();
|
|
|
|
})
|
2015-06-02 07:33:05 +00:00
|
|
|
|
2015-08-08 21:26:42 +00:00
|
|
|
it("should not copy an item or its attachment to a group twice", function* () {
|
|
|
|
var group = yield getGroup();
|
|
|
|
|
|
|
|
var itemTitle = Zotero.Utilities.randomString();
|
|
|
|
var item = yield createDataObject('item', false, { skipSelect: true });
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
file.append('test.png');
|
|
|
|
var attachment = yield Zotero.Attachments.importFromFile({
|
|
|
|
file: file,
|
|
|
|
parentItemID: item.id
|
|
|
|
});
|
|
|
|
var attachmentTitle = Zotero.Utilities.randomString();
|
|
|
|
attachment.setField('title', attachmentTitle);
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
|
|
|
yield drop('item', 'L' + group.libraryID, [item.id]);
|
|
|
|
assert.isFalse(yield canDrop('item', 'L' + group.libraryID, [item.id]));
|
|
|
|
})
|
2015-05-22 23:15:21 +00:00
|
|
|
|
2015-08-08 21:26:42 +00:00
|
|
|
it("should remove a linked, trashed item in a group from the trash and collections", function* () {
|
|
|
|
var group = yield getGroup();
|
|
|
|
var collection = yield createDataObject('collection', { libraryID: group.libraryID });
|
|
|
|
|
|
|
|
var item = yield createDataObject('item', false, { skipSelect: true });
|
|
|
|
yield drop('item', 'L' + group.libraryID, [item.id]);
|
|
|
|
|
2016-03-18 08:04:33 +00:00
|
|
|
var droppedItem = item.getLinkedItem(group.libraryID);
|
2015-08-08 21:26:42 +00:00
|
|
|
droppedItem.setCollections([collection.id]);
|
|
|
|
droppedItem.deleted = true;
|
|
|
|
yield droppedItem.saveTx();
|
|
|
|
|
|
|
|
// Add observer to wait for collection add
|
|
|
|
var deferred = Zotero.Promise.defer();
|
|
|
|
var observerID = Zotero.Notifier.registerObserver({
|
|
|
|
notify: function (event, type, ids) {
|
|
|
|
if (event == 'refresh' && type == 'trash' && ids[0] == group.libraryID) {
|
|
|
|
setTimeout(function () {
|
|
|
|
deferred.resolve();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 'trash', 'test');
|
|
|
|
yield drop('item', 'L' + group.libraryID, [item.id], deferred.promise);
|
|
|
|
Zotero.Notifier.unregisterObserver(observerID);
|
|
|
|
|
|
|
|
assert.isFalse(droppedItem.deleted);
|
|
|
|
// Should be removed from collections when removed from trash
|
|
|
|
assert.lengthOf(droppedItem.getCollections(), 0);
|
|
|
|
})
|
2015-05-22 23:15:21 +00:00
|
|
|
})
|
2015-05-24 01:10:07 +00:00
|
|
|
|
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
|
|
|
|
2015-08-08 21:26:42 +00:00
|
|
|
describe("with collections", function () {
|
|
|
|
it("should make a subcollection top-level", function* () {
|
|
|
|
var collection1 = yield createDataObject('collection', { name: "A" }, { skipSelect: true });
|
|
|
|
var collection2 = yield createDataObject('collection', { name: "C" }, { skipSelect: true });
|
|
|
|
var collection3 = yield createDataObject('collection', { name: "D" }, { skipSelect: true });
|
|
|
|
var collection4 = yield createDataObject('collection', { name: "B", parentKey: collection2.key });
|
|
|
|
|
|
|
|
var colIndex1 = cv.getRowIndexByID('C' + collection1.id);
|
|
|
|
var colIndex2 = cv.getRowIndexByID('C' + collection2.id);
|
|
|
|
var colIndex3 = cv.getRowIndexByID('C' + collection3.id);
|
|
|
|
var colIndex4 = cv.getRowIndexByID('C' + collection4.id);
|
|
|
|
|
|
|
|
// Add observer to wait for collection add
|
|
|
|
var deferred = Zotero.Promise.defer();
|
|
|
|
var observerID = Zotero.Notifier.registerObserver({
|
|
|
|
notify: function (event, type, ids, extraData) {
|
|
|
|
if (type == 'collection' && event == 'modify' && ids[0] == collection4.id) {
|
|
|
|
setTimeout(function () {
|
|
|
|
deferred.resolve();
|
|
|
|
}, 50);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 'collection', 'test');
|
|
|
|
|
|
|
|
yield drop(
|
|
|
|
'collection',
|
|
|
|
{
|
|
|
|
row: 0,
|
|
|
|
orient: 1
|
|
|
|
},
|
|
|
|
[collection4.id],
|
|
|
|
deferred.promise
|
|
|
|
);
|
|
|
|
|
|
|
|
Zotero.Notifier.unregisterObserver(observerID);
|
|
|
|
|
|
|
|
var newColIndex1 = cv.getRowIndexByID('C' + collection1.id);
|
|
|
|
var newColIndex2 = cv.getRowIndexByID('C' + collection2.id);
|
|
|
|
var newColIndex3 = cv.getRowIndexByID('C' + collection3.id);
|
|
|
|
var newColIndex4 = cv.getRowIndexByID('C' + collection4.id);
|
|
|
|
|
|
|
|
assert.equal(newColIndex1, colIndex1);
|
|
|
|
assert.isBelow(newColIndex4, newColIndex2);
|
|
|
|
assert.isBelow(newColIndex2, newColIndex3);
|
|
|
|
assert.equal(cv.getRow(newColIndex4).level, cv.getRow(newColIndex1).level);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should move a subcollection and its subcollection down under another collection", function* () {
|
|
|
|
var collectionA = yield createDataObject('collection', { name: "A" }, { skipSelect: true });
|
|
|
|
var collectionB = yield createDataObject('collection', { name: "B", parentKey: collectionA.key });
|
|
|
|
var collectionC = yield createDataObject('collection', { name: "C", parentKey: collectionB.key });
|
|
|
|
var collectionD = yield createDataObject('collection', { name: "D" }, { skipSelect: true });
|
|
|
|
var collectionE = yield createDataObject('collection', { name: "E" }, { skipSelect: true });
|
|
|
|
var collectionF = yield createDataObject('collection', { name: "F" }, { skipSelect: true });
|
|
|
|
var collectionG = yield createDataObject('collection', { name: "G", parentKey: collectionD.key });
|
|
|
|
var collectionH = yield createDataObject('collection', { name: "H", parentKey: collectionG.key });
|
|
|
|
|
|
|
|
var colIndexA = cv.getRowIndexByID('C' + collectionA.id);
|
|
|
|
var colIndexB = cv.getRowIndexByID('C' + collectionB.id);
|
|
|
|
var colIndexC = cv.getRowIndexByID('C' + collectionC.id);
|
|
|
|
var colIndexD = cv.getRowIndexByID('C' + collectionD.id);
|
|
|
|
var colIndexE = cv.getRowIndexByID('C' + collectionE.id);
|
|
|
|
var colIndexF = cv.getRowIndexByID('C' + collectionF.id);
|
|
|
|
var colIndexG = cv.getRowIndexByID('C' + collectionG.id);
|
|
|
|
var colIndexH = cv.getRowIndexByID('C' + collectionH.id);
|
|
|
|
|
|
|
|
yield cv.selectCollection(collectionG.id);
|
|
|
|
|
|
|
|
// Add observer to wait for collection add
|
|
|
|
var deferred = Zotero.Promise.defer();
|
|
|
|
var observerID = Zotero.Notifier.registerObserver({
|
|
|
|
notify: function (event, type, ids, extraData) {
|
|
|
|
if (type == 'collection' && event == 'modify' && ids[0] == collectionG.id) {
|
|
|
|
setTimeout(function () {
|
|
|
|
deferred.resolve();
|
|
|
|
}, 50);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 'collection', 'test');
|
|
|
|
|
|
|
|
yield drop(
|
|
|
|
'collection',
|
|
|
|
{
|
|
|
|
row: colIndexE,
|
|
|
|
orient: 0
|
|
|
|
},
|
|
|
|
[collectionG.id],
|
|
|
|
deferred.promise
|
|
|
|
);
|
|
|
|
|
|
|
|
Zotero.Notifier.unregisterObserver(observerID);
|
|
|
|
|
|
|
|
var newColIndexA = cv.getRowIndexByID('C' + collectionA.id);
|
|
|
|
var newColIndexB = cv.getRowIndexByID('C' + collectionB.id);
|
|
|
|
var newColIndexC = cv.getRowIndexByID('C' + collectionC.id);
|
|
|
|
var newColIndexD = cv.getRowIndexByID('C' + collectionD.id);
|
|
|
|
var newColIndexE = cv.getRowIndexByID('C' + collectionE.id);
|
|
|
|
var newColIndexF = cv.getRowIndexByID('C' + collectionF.id);
|
|
|
|
var newColIndexG = cv.getRowIndexByID('C' + collectionG.id);
|
|
|
|
var newColIndexH = cv.getRowIndexByID('C' + collectionH.id);
|
|
|
|
|
|
|
|
assert.isFalse(cv.isContainerOpen(newColIndexD));
|
|
|
|
assert.isTrue(cv.isContainerEmpty(newColIndexD));
|
|
|
|
assert.isTrue(cv.isContainerOpen(newColIndexE));
|
|
|
|
assert.isFalse(cv.isContainerEmpty(newColIndexE));
|
|
|
|
assert.equal(newColIndexE, newColIndexG - 1);
|
|
|
|
assert.equal(newColIndexG, newColIndexH - 1);
|
|
|
|
|
|
|
|
// TODO: Check deeper subcollection open states
|
|
|
|
})
|
2016-02-01 14:59:30 +00:00
|
|
|
|
|
|
|
it("should move a subcollection and its subcollection up under another collection", function* () {
|
|
|
|
var collectionA = yield createDataObject('collection', { name: "A" }, { skipSelect: true });
|
|
|
|
var collectionB = yield createDataObject('collection', { name: "B", parentKey: collectionA.key });
|
|
|
|
var collectionC = yield createDataObject('collection', { name: "C", parentKey: collectionB.key });
|
|
|
|
var collectionD = yield createDataObject('collection', { name: "D" }, { skipSelect: true });
|
|
|
|
var collectionE = yield createDataObject('collection', { name: "E" }, { skipSelect: true });
|
|
|
|
var collectionF = yield createDataObject('collection', { name: "F" }, { skipSelect: true });
|
|
|
|
var collectionG = yield createDataObject('collection', { name: "G", parentKey: collectionE.key });
|
|
|
|
var collectionH = yield createDataObject('collection', { name: "H", parentKey: collectionG.key });
|
|
|
|
|
|
|
|
var colIndexA = cv.getRowIndexByID('C' + collectionA.id);
|
|
|
|
var colIndexB = cv.getRowIndexByID('C' + collectionB.id);
|
|
|
|
var colIndexC = cv.getRowIndexByID('C' + collectionC.id);
|
|
|
|
var colIndexD = cv.getRowIndexByID('C' + collectionD.id);
|
|
|
|
var colIndexE = cv.getRowIndexByID('C' + collectionE.id);
|
|
|
|
var colIndexF = cv.getRowIndexByID('C' + collectionF.id);
|
|
|
|
var colIndexG = cv.getRowIndexByID('C' + collectionG.id);
|
|
|
|
var colIndexH = cv.getRowIndexByID('C' + collectionH.id);
|
|
|
|
|
|
|
|
yield cv.selectCollection(collectionG.id);
|
|
|
|
|
|
|
|
// Add observer to wait for collection add
|
|
|
|
var deferred = Zotero.Promise.defer();
|
|
|
|
var observerID = Zotero.Notifier.registerObserver({
|
|
|
|
notify: function (event, type, ids, extraData) {
|
|
|
|
if (type == 'collection' && event == 'modify' && ids[0] == collectionG.id) {
|
|
|
|
setTimeout(function () {
|
|
|
|
deferred.resolve();
|
|
|
|
}, 50);
|
|
|
|
}
|
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
|
|
|
}
|
2016-02-01 14:59:30 +00:00
|
|
|
}, 'collection', 'test');
|
|
|
|
|
|
|
|
yield drop(
|
|
|
|
'collection',
|
|
|
|
{
|
|
|
|
row: colIndexD,
|
|
|
|
orient: 0
|
|
|
|
},
|
|
|
|
[collectionG.id],
|
|
|
|
deferred.promise
|
|
|
|
);
|
|
|
|
|
|
|
|
Zotero.Notifier.unregisterObserver(observerID);
|
|
|
|
|
|
|
|
var newColIndexA = cv.getRowIndexByID('C' + collectionA.id);
|
|
|
|
var newColIndexB = cv.getRowIndexByID('C' + collectionB.id);
|
|
|
|
var newColIndexC = cv.getRowIndexByID('C' + collectionC.id);
|
|
|
|
var newColIndexD = cv.getRowIndexByID('C' + collectionD.id);
|
|
|
|
var newColIndexE = cv.getRowIndexByID('C' + collectionE.id);
|
|
|
|
var newColIndexF = cv.getRowIndexByID('C' + collectionF.id);
|
|
|
|
var newColIndexG = cv.getRowIndexByID('C' + collectionG.id);
|
|
|
|
var newColIndexH = cv.getRowIndexByID('C' + collectionH.id);
|
|
|
|
|
|
|
|
assert.isFalse(cv.isContainerOpen(newColIndexE));
|
|
|
|
assert.isTrue(cv.isContainerEmpty(newColIndexE));
|
|
|
|
assert.isTrue(cv.isContainerOpen(newColIndexD));
|
|
|
|
assert.isFalse(cv.isContainerEmpty(newColIndexD));
|
|
|
|
assert.equal(newColIndexD, newColIndexG - 1);
|
|
|
|
assert.equal(newColIndexG, newColIndexH - 1);
|
|
|
|
|
|
|
|
// TODO: Check deeper subcollection open states
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
describe("with feed items", function () {
|
|
|
|
it('should add a translated feed item recovered from an URL', function* (){
|
|
|
|
var feed = yield createFeed();
|
|
|
|
var collection = yield createDataObject('collection', false, { skipSelect: true });
|
2016-02-11 11:02:38 +00:00
|
|
|
var url = getTestDataUrl('metadata/journalArticle-single.html');
|
2016-02-01 14:59:30 +00:00
|
|
|
var feedItem = yield createDataObject('feedItem', {libraryID: feed.libraryID}, { skipSelect: true });
|
|
|
|
feedItem.setField('url', url);
|
2016-02-11 11:02:38 +00:00
|
|
|
yield feedItem.saveTx();
|
2016-02-01 14:59:30 +00:00
|
|
|
var translateFn = sinon.spy(feedItem, 'translate');
|
|
|
|
|
|
|
|
// Add observer to wait for collection add
|
|
|
|
var deferred = Zotero.Promise.defer();
|
|
|
|
var itemIds;
|
|
|
|
|
|
|
|
var ids = (yield drop('item', 'C' + collection.id, [feedItem.id])).ids;
|
|
|
|
|
|
|
|
// Check that the translated item was the one that was created after drag
|
|
|
|
var item;
|
|
|
|
yield translateFn.returnValues[0].then(function(i) {
|
|
|
|
item = i;
|
|
|
|
assert.equal(item.id, ids[0]);
|
|
|
|
});
|
|
|
|
|
|
|
|
yield cv.selectCollection(collection.id);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var itemsView = win.ZoteroPane.itemsView;
|
|
|
|
assert.equal(itemsView.rowCount, 1);
|
|
|
|
var treeRow = itemsView.getRow(0);
|
|
|
|
assert.equal(treeRow.ref.id, item.id);
|
|
|
|
})
|
2015-05-24 01:10:07 +00:00
|
|
|
})
|
2015-05-22 23:15:21 +00:00
|
|
|
})
|
2015-04-30 21:06:38 +00:00
|
|
|
})
|