commit 61cb01b7c20819669a377abab7369dfc98273ffe
parent a2d4b050644f4e93e875d4c0f75bcd6376cc4cd5
Author: Dan Stillman <dstillman@zotero.org>
Date: Fri, 22 May 2015 19:15:21 -0400
Collection/item tree selection improvements
Wait for the pane's collectionSelected() to finish before returning from
collectionTreeView select methods (e.g., selectLibrary()), and wait for
previous items view to finish loading before creating a new one in
collectionSelected(). This ensures that the items view has been created (though
not loaded) before returning from a select. The tree can still get a bit
confused switching between collections, but I think we're getting closer to
fixing that.
Also switch the items tree to use the same pattern.
This also fixes dragging items to collections (#731).
Diffstat:
6 files changed, 295 insertions(+), 188 deletions(-)
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -71,7 +71,6 @@ Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
});
-
/*
* Called by the tree itself
*/
@@ -248,6 +247,29 @@ Zotero.CollectionTreeView.prototype.reload = function()
}.bind(this));
}
+
+/**
+ * Select a row and wait for its items view to be created
+ *
+ * Note that this doesn't wait for the items view to be loaded. For that, add a 'load' event
+ * listener to the items view.
+ *
+ * @param {Integer} row
+ * @return {Promise}
+ */
+Zotero.CollectionTreeView.prototype.selectWait = Zotero.Promise.method(function (row) {
+ if (this.selection.selectEventsSuppressed) {
+ this.selection.select(row);
+ return;
+ }
+ var deferred = Zotero.Promise.defer();
+ this.addEventListener('select', () => deferred.resolve());
+ this.selection.select(row);
+ return deferred.promise;
+});
+
+
+
/*
* Called by Zotero.Notifier on any changes to collections in the data layer
*/
@@ -389,7 +411,10 @@ Zotero.CollectionTreeView.prototype.notify = Zotero.Promise.coroutine(function*
}
}
+ var deferred = Zotero.Promise.defer();
+ this.addEventListener('select', () => deferred.resolve());
this.selection.selectEventsSuppressed = false;
+ return deferred.promise;
});
/**
@@ -823,7 +848,7 @@ Zotero.CollectionTreeView.prototype.selectByID = Zotero.Promise.coroutine(functi
switch (type) {
case 'L':
- this.selectLibrary(id);
+ yield this.selectLibrary(id);
return;
case 'C':
@@ -841,14 +866,14 @@ Zotero.CollectionTreeView.prototype.selectByID = Zotero.Promise.coroutine(functi
}
var row = this._rowMap[type + id];
this._treebox.ensureRowIsVisible(row);
- this.selection.select(row);
+ yield this.selectWait(row);
});
/**
* @param {Integer} libraryID Library to select
*/
-Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
+Zotero.CollectionTreeView.prototype.selectLibrary = Zotero.Promise.coroutine(function* (libraryID) {
if (Zotero.suppressUIUpdates) {
Zotero.debug("UI updates suppressed -- not changing library selection");
return false;
@@ -857,7 +882,7 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
// Select local library
if (!libraryID) {
this._treebox.ensureRowIsVisible(0);
- this.selection.select(0);
+ yield this.selectWait(0);
return true;
}
@@ -874,12 +899,12 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
var row = this._rowMap['L' + libraryID];
if (row !== undefined) {
this._treebox.ensureRowIsVisible(row);
- this.selection.select(row);
+ this.selectWait(row);
return true;
}
return false;
-}
+});
Zotero.CollectionTreeView.prototype.selectCollection = function (id) {
diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js
@@ -360,23 +360,22 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI
yield this.loadChildItems();
var current = this.getChildItems(true);
- return Zotero.DB.executeTransaction(function* () {
- for (let i=0; i<itemIDs.length; i++) {
- let itemID = itemIDs[i];
-
- if (current && current.indexOf(itemID) != -1) {
- Zotero.debug("Item " + itemID + " already a child of collection " + this.id);
- continue;
- }
-
- let item = yield this.ChildObjects.getAsync(itemID);
- yield item.loadCollections();
- item.addToCollection(this.id);
- yield item.save({
- skipDateModifiedUpdate: true
- });
+ Zotero.DB.requireTransaction();
+ for (let i = 0; i < itemIDs.length; i++) {
+ let itemID = itemIDs[i];
+
+ if (current && current.indexOf(itemID) != -1) {
+ Zotero.debug("Item " + itemID + " already a child of collection " + this.id);
+ continue;
}
- }.bind(this));
+
+ let item = yield this.ChildObjects.getAsync(itemID);
+ yield item.loadCollections();
+ item.addToCollection(this.id);
+ yield item.save({
+ skipDateModifiedUpdate: true
+ });
+ }
yield this.loadChildItems(true);
});
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -68,7 +68,7 @@ Zotero.ItemTreeView.prototype.type = 'item';
Zotero.ItemTreeView.prototype.setTree = Zotero.serial(Zotero.Promise.coroutine(function* (treebox)
{
try {
- Zotero.debug("Setting item tree");
+ Zotero.debug("Setting tree for items view " + this.id);
var start = Date.now();
// Try to set the window document if not yet set
if (treebox && !this._ownerDocument) {
@@ -256,7 +256,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.serial(Zotero.Promise.coroutine(f
this.collectionTreeRow.itemToSelect = null;
}
- Zotero.debug("Set tree in "+(Date.now()-start)+" ms");
+ Zotero.debug("Set tree for items view " + this.id + " in " + (Date.now() - start) + " ms");
this._initialized = true;
yield this._runListeners('load');
@@ -434,7 +434,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
yield this._refreshPromise;
if (!this._treebox || !this._treebox.treeBody) {
- Components.utils.reportError("Treebox didn't exist in itemTreeView.notify()");
+ Zotero.debug("Treebox didn't exist in itemTreeView.notify()");
return;
}
@@ -934,12 +934,13 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
//this._treebox.endUpdateBatch();
if (madeChanges) {
- var promise = this._getItemSelectedPromise();
+ var deferred = Zotero.Promise.defer();
+ this.addEventListener('select', () => deferred.resolve());
}
this.selection.selectEventsSuppressed = false;
if (madeChanges) {
Zotero.debug("Yielding for select promise"); // TEMP
- yield promise;
+ return deferred.promise;
}
});
@@ -1630,6 +1631,12 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
return false;
}
+ var selected = this.getSelectedItems(true);
+ if (selected.length == 1 && selected[0] == id) {
+ Zotero.debug("Item " + id + " is already selected");
+ return;
+ }
+
var row = this._rowMap[id];
// Get the row of the parent, if there is one
@@ -1674,17 +1681,20 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
row = this._rowMap[id];
}
- // This function calls nsITreeSelection.select(), which triggers the <tree>'s 'onselect'
- // attribute, which calls ZoteroPane.itemSelected(), which calls ZoteroItemPane.viewItem(),
- // which refreshes the itembox. But since the 'onselect' doesn't handle promises,
- // itemSelected() isn't waited for and 'yield selectItem(itemID)' continues before the
- // itembox has been refreshed. To get around this, we make a promise resolver that's
- // triggered by itemSelected() when it's done.
- if (!this.selection.selectEventsSuppressed) {
- var itemSelectedPromise = this._getItemSelectedPromise(id);
+ // this.selection.select() triggers the <tree>'s 'onselect' attribute, which calls
+ // ZoteroPane.itemSelected(), which calls ZoteroItemPane.viewItem(), which refreshes the
+ // itembox. But since the 'onselect' doesn't handle promises, itemSelected() isn't waited for
+ // here, which means that 'yield selectItem(itemID)' continues before the itembox has been
+ // refreshed. To get around this, we wait for a select event that's triggered by
+ // itemSelected() when it's done.
+ if (this.selection.selectEventsSuppressed) {
+ this.selection.select(row);
+ }
+ else {
+ var deferred = Zotero.Promise.defer();
+ this.addEventListener('select', () => deferred.resolve());
+ this.selection.select(row);
}
-
- this.selection.select(row);
// If |expand|, open row if container
if (expand && this.isContainer(row) && !this.isContainerOpen(row)) {
@@ -1692,8 +1702,8 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
}
this.selection.select(row);
- if (!this.selection.selectEventsSuppressed) {
- yield itemSelectedPromise;
+ if (deferred) {
+ yield deferred.promise;
}
// We aim for a row 5 below the target row, since ensureRowIsVisible() does
@@ -1718,23 +1728,6 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
});
-Zotero.ItemTreeView.prototype._getItemSelectedPromise = function (itemID) {
- if (itemID) {
- var selected = this.getSelectedItems(true);
- var alreadySelected = selected.length == 1 && selected[0] == itemID;
- if (alreadySelected) {
- return Zotero.Promise.resolve();
- }
- }
- return new Zotero.Promise(function () {
- this._itemSelectedPromiseResolver = {
- resolve: arguments[0],
- reject: arguments[1]
- };
- }.bind(this));
-}
-
-
/**
* Select multiple top-level items
*
diff --git a/chrome/content/zotero/xpcom/libraryTreeView.js b/chrome/content/zotero/xpcom/libraryTreeView.js
@@ -26,10 +26,14 @@
Zotero.LibraryTreeView = function () {
this._initialized = false;
this._listeners = {
- load: []
+ load: [],
+ select: []
};
this._rows = [];
this._rowMap = {};
+
+ this.id = Zotero.Utilities.randomString();
+ Zotero.debug("Creating " + this.type + "s view with id " + this.id);
};
Zotero.LibraryTreeView.prototype = {
@@ -43,10 +47,17 @@ Zotero.LibraryTreeView.prototype = {
this._listeners[event].push(listener);
}
}
+ else {
+ if (!this._listeners[event]) {
+ this._listeners[event] = [];
+ }
+ this._listeners[event].push(listener);
+ }
},
_runListeners: Zotero.Promise.coroutine(function* (event) {
+ if (!this._listeners[event]) return;
var listener;
while (listener = this._listeners[event].shift()) {
yield Zotero.Promise.resolve(listener());
@@ -55,7 +66,9 @@ Zotero.LibraryTreeView.prototype = {
/**
- * Returns a reference to the tree row at a given row
+ * Return a reference to the tree row at a given row
+ *
+ * @return {Zotero.CollectionTreeRow|Zotero.ItemTreeRow}
*/
getRow: function(row) {
return this._rows[row];
@@ -63,6 +76,24 @@ Zotero.LibraryTreeView.prototype = {
/**
+ * Return the index of the row with a given ID (e.g., "C123" for collection 123)
+ *
+ * @param {String} - Row id
+ * @return {Integer}
+ */
+ getRowByID: function (id) {
+ var type = id[0];
+ id = ('' + id).substr(1);
+ return this._rowMap[type + id];
+ },
+
+
+ onSelect: function () {
+ return this._runListeners('select');
+ },
+
+
+ /**
* Add a tree row to the main array, update the row count, tell the treebox that the row
* count changed, and update the row map
*
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -1107,110 +1107,127 @@ var ZoteroPane = new function()
});
- this.onCollectionSelected = Zotero.Promise.coroutine(function* () {
- var collectionTreeRow = this.getCollectionTreeRow();
- if (!collectionTreeRow) {
- return;
- }
-
- if (this.itemsView && this.itemsView.collectionTreeRow == collectionTreeRow) {
- Zotero.debug("Collection selection hasn't changed");
- return;
- }
-
- if (this.itemsView) {
- this.itemsView.unregister();
- document.getElementById('zotero-items-tree').view = this.itemsView = null;
- }
-
- if (this.collectionsView.selection.count != 1) {
- return;
- }
-
- // Clear quick search and tag selector when switching views
- document.getElementById('zotero-tb-search').value = "";
-
- // XBL functions might not yet be available
- var tagSelector = document.getElementById('zotero-tag-selector');
- if (tagSelector.clearAll) {
- tagSelector.clearAll();
- }
-
- // Not necessary with seltype="cell", which calls nsITreeView::isSelectable()
- /*if (collectionTreeRow.isSeparator()) {
- document.getElementById('zotero-items-tree').view = this.itemsView = null;
- return;
- }*/
-
- collectionTreeRow.setSearch('');
- collectionTreeRow.setTags(getTagSelection());
-
- // Enable or disable toolbar icons and menu options as necessary
- const disableIfNoEdit = [
- "cmd_zotero_newCollection",
- "cmd_zotero_newSavedSearch",
- "zotero-tb-add",
- "cmd_zotero_newItemFromCurrentPage",
- "zotero-tb-lookup",
- "cmd_zotero_newStandaloneNote",
- "zotero-tb-note-add",
- "zotero-tb-attachment-add"
- ];
- for(var i=0; i<disableIfNoEdit.length; i++) {
- var el = document.getElementById(disableIfNoEdit[i]);
+ this.onCollectionSelected = function () {
+ return Zotero.spawn(function* () {
+ var collectionTreeRow = this.getCollectionTreeRow();
+ if (!collectionTreeRow) {
+ return;
+ }
- // If a trash is selected, new collection depends on the
- // editability of the library
- if (collectionTreeRow.isTrash() &&
- disableIfNoEdit[i] == 'cmd_zotero_newCollection') {
- var overrideEditable = Zotero.Libraries.isEditable(collectionTreeRow.ref.libraryID);
+ if (this.itemsView && this.itemsView.collectionTreeRow == collectionTreeRow) {
+ Zotero.debug("Collection selection hasn't changed");
+ return;
}
- else {
- var overrideEditable = false;
+
+ if (this.itemsView) {
+ // Wait for existing items view to finish loading before unloading it
+ //
+ // TODO: Cancel loading
+ let deferred = Zotero.Promise.defer();
+ this.itemsView.addEventListener('load', function () {
+ deferred.resolve();
+ });
+ if (deferred.promise.isPending()) {
+ Zotero.debug("Waiting for items view " + this.itemsView.id + " to finish loading");
+ }
+ yield deferred.promise;
+
+ this.itemsView.unregister();
+ document.getElementById('zotero-items-tree').view = this.itemsView = null;
}
- if (collectionTreeRow.editable || overrideEditable) {
- if(el.hasAttribute("disabled")) el.removeAttribute("disabled");
- } else {
- el.setAttribute("disabled", "true");
+ if (this.collectionsView.selection.count != 1) {
+ return;
}
- }
-
- this.itemsView = new Zotero.ItemTreeView(collectionTreeRow);
- this.itemsView.onError = function () {
- ZoteroPane_Local.displayErrorMessage();
- };
- // If any queued load listeners, set them to run when the tree is ready
- if (this._listeners.itemsLoaded) {
- let listener;
- while (listener = this._listeners.itemsLoaded.shift()) {
- this.itemsView.addEventListener('load', listener);
+
+ // Clear quick search and tag selector when switching views
+ document.getElementById('zotero-tb-search').value = "";
+
+ // XBL functions might not yet be available
+ var tagSelector = document.getElementById('zotero-tag-selector');
+ if (tagSelector.clearAll) {
+ tagSelector.clearAll();
}
- }
- this.itemsView.addEventListener('load', this.setTagScope);
- document.getElementById('zotero-items-tree').view = this.itemsView;
-
- // Add events to treecolpicker to update menu before showing/hiding
- try {
- let treecols = document.getElementById('zotero-items-columns-header');
- let treecolpicker = treecols.boxObject.firstChild.nextSibling;
- let menupopup = treecolpicker.boxObject.firstChild.nextSibling;
- let attr = menupopup.getAttribute('onpopupshowing');
- if (attr.indexOf('Zotero') == -1) {
- menupopup.setAttribute('onpopupshowing', 'ZoteroPane.itemsView.onColumnPickerShowing(event);')
- // Keep whatever else is there
- + ' ' + attr;
- menupopup.setAttribute('onpopuphidden', 'ZoteroPane.itemsView.onColumnPickerHidden(event);')
- // Keep whatever else is there
- + ' ' + menupopup.getAttribute('onpopuphidden');
+
+ // Not necessary with seltype="cell", which calls nsITreeView::isSelectable()
+ /*if (collectionTreeRow.isSeparator()) {
+ document.getElementById('zotero-items-tree').view = this.itemsView = null;
+ return;
+ }*/
+
+ collectionTreeRow.setSearch('');
+ collectionTreeRow.setTags(getTagSelection());
+
+ // Enable or disable toolbar icons and menu options as necessary
+ const disableIfNoEdit = [
+ "cmd_zotero_newCollection",
+ "cmd_zotero_newSavedSearch",
+ "zotero-tb-add",
+ "cmd_zotero_newItemFromCurrentPage",
+ "zotero-tb-lookup",
+ "cmd_zotero_newStandaloneNote",
+ "zotero-tb-note-add",
+ "zotero-tb-attachment-add"
+ ];
+ for(var i=0; i<disableIfNoEdit.length; i++) {
+ var el = document.getElementById(disableIfNoEdit[i]);
+
+ // If a trash is selected, new collection depends on the
+ // editability of the library
+ if (collectionTreeRow.isTrash() &&
+ disableIfNoEdit[i] == 'cmd_zotero_newCollection') {
+ var overrideEditable = Zotero.Libraries.isEditable(collectionTreeRow.ref.libraryID);
+ }
+ else {
+ var overrideEditable = false;
+ }
+
+ if (collectionTreeRow.editable || overrideEditable) {
+ if(el.hasAttribute("disabled")) el.removeAttribute("disabled");
+ } else {
+ el.setAttribute("disabled", "true");
+ }
}
- }
- catch (e) {
- Zotero.debug(e);
- }
-
- Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
- });
+
+ this.itemsView = new Zotero.ItemTreeView(collectionTreeRow);
+ this.itemsView.onError = function () {
+ ZoteroPane_Local.displayErrorMessage();
+ };
+ // If any queued load listeners, set them to run when the tree is ready
+ if (this._listeners.itemsLoaded) {
+ let listener;
+ while (listener = this._listeners.itemsLoaded.shift()) {
+ this.itemsView.addEventListener('load', listener);
+ }
+ }
+ this.itemsView.addEventListener('load', this.setTagScope);
+ document.getElementById('zotero-items-tree').view = this.itemsView;
+
+ // Add events to treecolpicker to update menu before showing/hiding
+ try {
+ let treecols = document.getElementById('zotero-items-columns-header');
+ let treecolpicker = treecols.boxObject.firstChild.nextSibling;
+ let menupopup = treecolpicker.boxObject.firstChild.nextSibling;
+ let attr = menupopup.getAttribute('onpopupshowing');
+ if (attr.indexOf('Zotero') == -1) {
+ menupopup.setAttribute('onpopupshowing', 'ZoteroPane.itemsView.onColumnPickerShowing(event);')
+ // Keep whatever else is there
+ + ' ' + attr;
+ menupopup.setAttribute('onpopuphidden', 'ZoteroPane.itemsView.onColumnPickerHidden(event);')
+ // Keep whatever else is there
+ + ' ' + menupopup.getAttribute('onpopuphidden');
+ }
+ }
+ catch (e) {
+ Zotero.debug(e);
+ }
+
+ Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
+ }, this)
+ .finally(function () {
+ return this.collectionsView.onSelect();
+ }.bind(this));
+ };
this.getCollectionTreeRow = function () {
@@ -1419,19 +1436,8 @@ var ZoteroPane = new function()
return true;
}, this)
- .then(function (result) {
- // See note in itemTreeView.js::selectItem()
- if (this.itemsView && this.itemsView._itemSelectedPromiseResolver) {
- this.itemsView._itemSelectedPromiseResolver.resolve();
- }
- return result;
- }.bind(this))
- .catch(function (e) {
- Zotero.debug(e, 1);
- if (this.itemsView && this.itemsView._itemSelectedPromiseResolver) {
- this.itemsView._itemSelectedPromiseResolver.reject(e);
- }
- throw e;
+ .finally(function () {
+ return this.itemsView.onSelect();
}.bind(this));
}
@@ -2052,11 +2058,11 @@ var ZoteroPane = new function()
if (!selected) {
if (item.deleted) {
Zotero.debug("Item is deleted; switching to trash");
- self.collectionsView.selectTrash(item.libraryID);
+ yield self.collectionsView.selectTrash(item.libraryID);
}
else {
Zotero.debug("Item was not selected; switching to library");
- self.collectionsView.selectLibrary(item.libraryID);
+ yield self.collectionsView.selectLibrary(item.libraryID);
}
yield self.itemsView.selectItem(itemID, expand);
}
@@ -2071,12 +2077,12 @@ var ZoteroPane = new function()
// If in a different library
if (item.libraryID != currentLibraryID) {
Zotero.debug("Library ID differs; switching library");
- self.collectionsView.selectLibrary(item.libraryID);
+ yield self.collectionsView.selectLibrary(item.libraryID);
}
// Force switch to library view
else if (!self.collectionsView.selectedTreeRow.isLibrary() && inLibrary) {
Zotero.debug("Told to select in library; switching to library");
- self.collectionsView.selectLibrary(item.libraryID);
+ yield self.collectionsView.selectLibrary(item.libraryID);
}
}
catch (e) {
diff --git a/test/tests/collectionTreeViewTest.js b/test/tests/collectionTreeViewTest.js
@@ -3,27 +3,31 @@
describe("Zotero.CollectionTreeView", function() {
var win, collectionsView;
+ // Select library
+ // TODO: Add a selectCollection() function and select a collection instead
+ var resetSelection = Zotero.Promise.coroutine(function* () {
+ yield collectionsView.selectLibrary(Zotero.Libraries.userLibraryID);
+ yield waitForItemsLoad(win);
+ assert.equal(collectionsView.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
+ });
+
// Load Zotero pane and select library
before(function* () {
win = yield loadZoteroPane();
collectionsView = win.ZoteroPane.collectionsView;
});
+ beforeEach(function () {
+ return resetSelection();
+ })
after(function () {
win.close();
});
- // Select library
- // TODO: Add a selectCollection() function and select a collection instead
- var resetSelection = function () {
- collectionsView.selectLibrary(Zotero.Libraries.userLibraryID);
- assert.equal(collectionsView.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
- }
-
describe("collapse/expand", function () {
it("should close and open My Library repeatedly", function* () {
var libraryID = Zotero.Libraries.userLibraryID;
var cv = collectionsView;
- cv.selectLibrary(libraryID);
+ yield cv.selectLibrary(libraryID);
var row = cv.selection.currentIndex;
cv.collapseLibrary(libraryID);
@@ -54,8 +58,6 @@ describe("Zotero.CollectionTreeView", function() {
describe("#notify()", function () {
it("should select a new collection", function* () {
- resetSelection();
-
// Create collection
var collection = new Zotero.Collection;
collection.name = "Select new collection";
@@ -67,8 +69,6 @@ describe("Zotero.CollectionTreeView", function() {
});
it("shouldn't select a new collection if skipNotifier is passed", function* () {
- resetSelection();
-
// Create collection with skipNotifier flag
var collection = new Zotero.Collection;
collection.name = "No select on skipNotifier";
@@ -81,8 +81,6 @@ describe("Zotero.CollectionTreeView", function() {
});
it("shouldn't select a new collection if skipSelect is passed", function* () {
- resetSelection();
-
// Create collection with skipSelect flag
var collection = new Zotero.Collection;
collection.name = "No select on skipSelect";
@@ -100,7 +98,7 @@ describe("Zotero.CollectionTreeView", function() {
collection.name = "No select on modify";
var id = yield collection.saveTx();
- resetSelection();
+ yield resetSelection();
collection.name = "No select on modify 2";
yield collection.saveTx();
@@ -157,4 +155,59 @@ describe("Zotero.CollectionTreeView", function() {
}
})
})
+
+ describe("#drop()", 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
+ });
+ var row = collectionsView.getRowByID("C" + collection.id);
+
+ // Add observer to wait for collection add
+ var deferred = Zotero.Promise.defer();
+ var observerID = Zotero.Notifier.registerObserver({
+ notify: function (event, type, ids) {
+ if (type == 'collection-item' && event == 'add'
+ && ids[0] == collection.id + "-" + item.id) {
+ setTimeout(function () {
+ deferred.resolve();
+ });
+ }
+ }
+ }, 'collection-item', 'test');
+
+ // Simulate a drag and drop
+ var stub = sinon.stub(Zotero.DragDrop, "getDragTarget");
+ stub.returns(collectionsView.getRow(row));
+ collectionsView.drop(row, 0, {
+ dropEffect: 'copy',
+ effectAllowed: 'copy',
+ mozSourceNode: win.document.getElementById('zotero-items-tree'),
+ types: {
+ contains: function (type) {
+ return type == 'zotero/item';
+ }
+ },
+ getData: function (type) {
+ if (type == 'zotero/item') {
+ return "" + item.id;
+ }
+ }
+ })
+
+ yield deferred.promise;
+ stub.restore();
+ Zotero.Notifier.unregisterObserver(observerID);
+ yield collectionsView.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);
+ })
+ })
})