collectionTreeViewTest.js (39491B)
1 "use strict"; 2 3 describe("Zotero.CollectionTreeView", function() { 4 var win, zp, cv, userLibraryID; 5 6 before(function* () { 7 win = yield loadZoteroPane(); 8 zp = win.ZoteroPane; 9 cv = zp.collectionsView; 10 userLibraryID = Zotero.Libraries.userLibraryID; 11 }); 12 beforeEach(function () { 13 // TODO: Add a selectCollection() function and select a collection instead? 14 return selectLibrary(win); 15 }) 16 after(function () { 17 win.close(); 18 }); 19 20 describe("#refresh()", function () { 21 it("should show Duplicate Items and Unfiled Items by default", function* () { 22 Zotero.Prefs.clear('duplicateLibraries'); 23 Zotero.Prefs.clear('unfiledLibraries'); 24 yield cv.refresh(); 25 assert.ok(cv.getRowIndexByID("D" + userLibraryID)); 26 assert.ok(cv.getRowIndexByID("U" + userLibraryID)); 27 }); 28 29 it("shouldn't show Duplicate Items and Unfiled Items if hidden", function* () { 30 Zotero.Prefs.set('duplicateLibraries', `{"${userLibraryID}": false}`); 31 Zotero.Prefs.set('unfiledLibraries', `{"${userLibraryID}": false}`); 32 yield cv.refresh(); 33 assert.isFalse(cv.getRowIndexByID("D" + userLibraryID)); 34 assert.isFalse(cv.getRowIndexByID("U" + userLibraryID)); 35 }); 36 37 it("should maintain open state of group", function* () { 38 var group1 = yield createGroup(); 39 var group2 = yield createGroup(); 40 var group1Row = cv.getRowIndexByID(group1.treeViewID); 41 var group2Row = cv.getRowIndexByID(group2.treeViewID); 42 43 // Open group 1 and close group 2 44 if (!cv.isContainerOpen(group1Row)) { 45 yield cv.toggleOpenState(group1Row); 46 } 47 if (cv.isContainerOpen(group2Row)) { 48 yield cv.toggleOpenState(group2Row); 49 } 50 // Don't wait for delayed save 51 cv._saveOpenStates(); 52 53 group1Row = cv.getRowIndexByID(group1.treeViewID); 54 group2Row = cv.getRowIndexByID(group2.treeViewID); 55 56 yield cv.refresh(); 57 58 // Group rows shouldn't have changed 59 assert.equal(cv.getRowIndexByID(group1.treeViewID), group1Row); 60 assert.equal(cv.getRowIndexByID(group2.treeViewID), group2Row); 61 // Group open states shouldn't have changed 62 assert.isTrue(cv.isContainerOpen(group1Row)); 63 assert.isFalse(cv.isContainerOpen(group2Row)); 64 }); 65 66 it("should update associated item tree view", function* () { 67 var collection = yield createDataObject('collection'); 68 var item = yield createDataObject('item', { collections: [collection.id] }); 69 yield cv.reload(); 70 yield cv.selectCollection(collection.id); 71 yield cv.selectItem(item.id); 72 }); 73 }); 74 75 describe("collapse/expand", function () { 76 it("should close and open My Library repeatedly", function* () { 77 yield cv.selectLibrary(userLibraryID); 78 var row = cv.selection.currentIndex; 79 80 cv.collapseLibrary(userLibraryID); 81 var nextRow = cv.getRow(row + 1); 82 assert.equal(cv.selection.currentIndex, row); 83 assert.ok(nextRow.isSeparator()); 84 assert.isFalse(cv.isContainerOpen(row)); 85 86 yield cv.expandLibrary(userLibraryID); 87 nextRow = cv.getRow(row + 1); 88 assert.equal(cv.selection.currentIndex, row); 89 assert.ok(!nextRow.isSeparator()); 90 assert.ok(cv.isContainerOpen(row)); 91 92 cv.collapseLibrary(userLibraryID); 93 nextRow = cv.getRow(row + 1); 94 assert.equal(cv.selection.currentIndex, row); 95 assert.ok(nextRow.isSeparator()); 96 assert.isFalse(cv.isContainerOpen(row)); 97 98 yield cv.expandLibrary(userLibraryID); 99 nextRow = cv.getRow(row + 1); 100 assert.equal(cv.selection.currentIndex, row); 101 assert.ok(!nextRow.isSeparator()); 102 assert.ok(cv.isContainerOpen(row)); 103 }) 104 }) 105 106 describe("#expandLibrary()", function () { 107 var libraryRow, col1, col2, col3; 108 109 before(function* () { 110 yield cv.selectLibrary(userLibraryID); 111 libraryRow = cv.selection.currentIndex; 112 }); 113 114 beforeEach(function* () { 115 // My Library 116 // - A 117 // - B 118 // - C 119 col1 = yield createDataObject('collection'); 120 col2 = yield createDataObject('collection', { parentID: col1.id }); 121 col3 = yield createDataObject('collection', { parentID: col2.id }); 122 }); 123 124 it("should open a library and respect stored container state", function* () { 125 // Collapse B 126 yield cv.toggleOpenState(cv.getRowIndexByID(col2.treeViewID)); 127 yield cv._saveOpenStates(); 128 129 // Close and reopen library 130 yield cv.toggleOpenState(libraryRow); 131 yield cv.expandLibrary(userLibraryID); 132 133 assert.ok(cv.getRowIndexByID(col1.treeViewID)) 134 assert.ok(cv.getRowIndexByID(col2.treeViewID)) 135 assert.isFalse(cv.getRowIndexByID(col3.treeViewID)) 136 }); 137 138 it("should open a library and all subcollections in recursive mode", function* () { 139 yield cv.toggleOpenState(cv.getRowIndexByID(col2.treeViewID)); 140 yield cv._saveOpenStates(); 141 142 // Close and reopen library 143 yield cv.toggleOpenState(libraryRow); 144 yield cv.expandLibrary(userLibraryID, true); 145 146 assert.ok(cv.getRowIndexByID(col1.treeViewID)) 147 assert.ok(cv.getRowIndexByID(col2.treeViewID)) 148 assert.ok(cv.getRowIndexByID(col3.treeViewID)) 149 }); 150 151 it("should open a group and show top-level collections", function* () { 152 var group = yield createGroup(); 153 var libraryID = group.libraryID; 154 var col1 = yield createDataObject('collection', { libraryID }); 155 var col2 = yield createDataObject('collection', { libraryID }); 156 var col3 = yield createDataObject('collection', { libraryID }); 157 var col4 = yield createDataObject('collection', { libraryID, parentID: col1.id }); 158 var col5 = yield createDataObject('collection', { libraryID, parentID: col4.id }); 159 160 // Close everything 161 [col4, col1, group].forEach(o => cv._closeContainer(cv.getRowIndexByID(o.treeViewID))); 162 163 yield cv.expandLibrary(libraryID); 164 assert.isNumber(cv.getRowIndexByID(col1.treeViewID)); 165 assert.isNumber(cv.getRowIndexByID(col2.treeViewID)); 166 assert.isNumber(cv.getRowIndexByID(col3.treeViewID)); 167 assert.isFalse(cv.getRowIndexByID(col4.treeViewID)); 168 assert.isFalse(cv.getRowIndexByID(col5.treeViewID)); 169 }); 170 }); 171 172 describe("#expandToCollection()", function () { 173 it("should expand a collection to a subcollection", function* () { 174 var collection1 = yield createDataObject('collection'); 175 var collection2 = createUnsavedDataObject('collection'); 176 collection2.parentID = collection1.id; 177 yield collection2.saveTx({ 178 skipSelect: true 179 }); 180 var row = cv.getRowIndexByID("C" + collection1.id); 181 assert.isFalse(cv.isContainerOpen(row)); 182 183 yield cv.expandToCollection(collection2.id); 184 185 // Make sure parent row position hasn't changed 186 assert.equal(cv.getRowIndexByID("C" + collection1.id), row); 187 // Parent should have been opened 188 assert.isTrue(cv.isContainerOpen(row)); 189 }) 190 }) 191 192 describe("#selectByID()", function () { 193 it("should select the trash", function* () { 194 yield cv.selectByID("T1"); 195 var row = cv.selection.currentIndex; 196 var treeRow = cv.getRow(row); 197 assert.ok(treeRow.isTrash()); 198 assert.equal(treeRow.ref.libraryID, userLibraryID); 199 }) 200 }) 201 202 describe("#selectWait()", function () { 203 it("shouldn't hang if row is already selected", function* () { 204 var row = cv.getRowIndexByID("T" + userLibraryID); 205 cv.selection.select(row); 206 yield Zotero.Promise.delay(50); 207 yield cv.selectWait(row); 208 }) 209 }) 210 211 describe("#notify()", function () { 212 it("should select a new collection", function* () { 213 // Create collection 214 var collection = new Zotero.Collection; 215 collection.name = "Select new collection"; 216 var id = yield collection.saveTx(); 217 218 // New collection should be selected 219 var selected = cv.getSelectedCollection(true); 220 assert.equal(selected, id); 221 }); 222 223 it("shouldn't select a new collection if skipNotifier is passed", function* () { 224 // Create collection with skipNotifier flag 225 var collection = new Zotero.Collection; 226 collection.name = "No select on skipNotifier"; 227 var id = yield collection.saveTx({ 228 skipNotifier: true 229 }); 230 231 // Library should still be selected 232 assert.equal(cv.getSelectedLibraryID(), userLibraryID); 233 }); 234 235 it("shouldn't select a new collection if skipSelect is passed", function* () { 236 // Create collection with skipSelect flag 237 var collection = new Zotero.Collection; 238 collection.name = "No select on skipSelect"; 239 var id = yield collection.saveTx({ 240 skipSelect: true 241 }); 242 243 // Library should still be selected 244 assert.equal(cv.getSelectedLibraryID(), userLibraryID); 245 }); 246 247 it("shouldn't select a modified collection", function* () { 248 // Create collection 249 var collection = new Zotero.Collection; 250 collection.name = "No select on modify"; 251 var id = yield collection.saveTx(); 252 253 yield selectLibrary(win); 254 255 collection.name = "No select on modify 2"; 256 yield collection.saveTx(); 257 258 // Modified collection should not be selected 259 assert.equal(cv.getSelectedLibraryID(), userLibraryID); 260 }); 261 262 it("should maintain selection on a selected modified collection", function* () { 263 // Create collection 264 var collection = new Zotero.Collection; 265 collection.name = "Reselect on modify"; 266 var id = yield collection.saveTx(); 267 268 var selected = cv.getSelectedCollection(true); 269 assert.equal(selected, id); 270 271 collection.name = "Reselect on modify 2"; 272 yield collection.saveTx(); 273 274 // Modified collection should still be selected 275 selected = cv.getSelectedCollection(true); 276 assert.equal(selected, id); 277 }); 278 279 it("should update the editability of the current view", function* () { 280 var group = yield createGroup({ 281 editable: false, 282 filesEditable: false 283 }); 284 yield cv.selectLibrary(group.libraryID); 285 yield waitForItemsLoad(win); 286 287 assert.isFalse(cv.selectedTreeRow.editable); 288 var cmd = win.document.getElementById('cmd_zotero_newStandaloneNote'); 289 assert.isTrue(cmd.getAttribute('disabled') == 'true'); 290 291 group.editable = true; 292 yield group.saveTx(); 293 294 assert.isTrue(cv.selectedTreeRow.editable); 295 assert.isFalse(cmd.getAttribute('disabled') == 'true'); 296 }); 297 298 it("should re-sort a modified collection", function* () { 299 var prefix = Zotero.Utilities.randomString() + " "; 300 var collectionA = yield createDataObject('collection', { name: prefix + "A" }); 301 var collectionB = yield createDataObject('collection', { name: prefix + "B" }); 302 303 var aRow = cv.getRowIndexByID("C" + collectionA.id); 304 var aRowOriginal = aRow; 305 var bRow = cv.getRowIndexByID("C" + collectionB.id); 306 assert.equal(bRow, aRow + 1); 307 308 collectionA.name = prefix + "C"; 309 yield collectionA.saveTx(); 310 311 var aRow = cv.getRowIndexByID("C" + collectionA.id); 312 var bRow = cv.getRowIndexByID("C" + collectionB.id); 313 assert.equal(bRow, aRowOriginal); 314 assert.equal(aRow, bRow + 1); 315 }) 316 317 it("should re-sort a modified search", function* () { 318 var prefix = Zotero.Utilities.randomString() + " "; 319 var searchA = yield createDataObject('search', { name: prefix + "A" }); 320 var searchB = yield createDataObject('search', { name: prefix + "B" }); 321 322 var aRow = cv.getRowIndexByID("S" + searchA.id); 323 var aRowOriginal = aRow; 324 var bRow = cv.getRowIndexByID("S" + searchB.id); 325 assert.equal(bRow, aRow + 1); 326 327 searchA.name = prefix + "C"; 328 yield searchA.saveTx(); 329 330 var aRow = cv.getRowIndexByID("S" + searchA.id); 331 var bRow = cv.getRowIndexByID("S" + searchB.id); 332 assert.equal(bRow, aRowOriginal); 333 assert.equal(aRow, bRow + 1); 334 }) 335 336 337 it("should add collection after parent's subcollection and before non-sibling", function* () { 338 var c0 = yield createDataObject('collection', { name: "Test" }); 339 var rootRow = cv.getRowIndexByID(c0.treeViewID); 340 341 var c1 = yield createDataObject('collection', { name: "1", parentID: c0.id }); 342 var c2 = yield createDataObject('collection', { name: "2", parentID: c0.id }); 343 var c3 = yield createDataObject('collection', { name: "3", parentID: c1.id }); 344 var c4 = yield createDataObject('collection', { name: "4", parentID: c3.id }); 345 var c5 = yield createDataObject('collection', { name: "5", parentID: c1.id }); 346 347 assert.equal(cv.getRowIndexByID(c1.treeViewID), rootRow + 1); 348 349 assert.isAbove(cv.getRowIndexByID(c1.treeViewID), cv.getRowIndexByID(c0.treeViewID)); 350 assert.isAbove(cv.getRowIndexByID(c2.treeViewID), cv.getRowIndexByID(c0.treeViewID)); 351 352 assert.isAbove(cv.getRowIndexByID(c3.treeViewID), cv.getRowIndexByID(c1.treeViewID)); 353 assert.isAbove(cv.getRowIndexByID(c5.treeViewID), cv.getRowIndexByID(c1.treeViewID)); 354 assert.isBelow(cv.getRowIndexByID(c5.treeViewID), cv.getRowIndexByID(c2.treeViewID)); 355 356 assert.equal(cv.getRowIndexByID(c4.treeViewID), cv.getRowIndexByID(c3.treeViewID) + 1); 357 }); 358 359 360 it("should add multiple collections", function* () { 361 var col1, col2; 362 yield Zotero.DB.executeTransaction(function* () { 363 col1 = createUnsavedDataObject('collection'); 364 col2 = createUnsavedDataObject('collection'); 365 yield col1.save(); 366 yield col2.save(); 367 }); 368 369 var aRow = cv.getRowIndexByID("C" + col1.id); 370 var bRow = cv.getRowIndexByID("C" + col2.id); 371 assert.isAbove(aRow, 0); 372 assert.isAbove(bRow, 0); 373 // skipSelect is implied for multiple collections, so library should still be selected 374 assert.equal(cv.selection.currentIndex, 0); 375 }); 376 377 378 it("shouldn't refresh the items list when a collection is modified", function* () { 379 var collection = yield createDataObject('collection'); 380 yield waitForItemsLoad(win); 381 var itemsView = zp.itemsView; 382 383 collection.name = "New Name"; 384 yield collection.saveTx(); 385 386 yield waitForItemsLoad(win); 387 assert.equal(zp.itemsView, itemsView); 388 }) 389 390 it("should add a saved search after collections", function* () { 391 var collection = new Zotero.Collection; 392 collection.name = "Test"; 393 var collectionID = yield collection.saveTx(); 394 395 var search = new Zotero.Search; 396 search.name = "A Test Search"; 397 search.addCondition('title', 'contains', 'test'); 398 var searchID = yield search.saveTx(); 399 400 var collectionRow = cv._rowMap["C" + collectionID]; 401 var searchRow = cv._rowMap["S" + searchID]; 402 var duplicatesRow = cv._rowMap["D" + userLibraryID]; 403 var unfiledRow = cv._rowMap["U" + userLibraryID]; 404 405 assert.isAbove(searchRow, collectionRow); 406 // If there's a duplicates row or an unfiled row, add before those. 407 // Otherwise, add before the trash 408 if (duplicatesRow !== undefined) { 409 assert.isBelow(searchRow, duplicatesRow); 410 } 411 else if (unfiledRow !== undefined) { 412 assert.isBelow(searchRow, unfiledRow); 413 } 414 else { 415 var trashRow = cv._rowMap["T" + userLibraryID]; 416 assert.isBelow(searchRow, trashRow); 417 } 418 }) 419 420 it("shouldn't select a new group", function* () { 421 var group = yield createGroup(); 422 // Library should still be selected 423 assert.equal(cv.getSelectedLibraryID(), userLibraryID); 424 }) 425 426 it("should remove a group and all children", function* () { 427 // Make sure Group Libraries separator and header exist already, 428 // since otherwise they'll interfere with the count 429 yield getGroup(); 430 431 var originalRowCount = cv.rowCount; 432 433 var group = yield createGroup(); 434 yield createDataObject('collection', { libraryID: group.libraryID }); 435 var c = yield createDataObject('collection', { libraryID: group.libraryID }); 436 yield createDataObject('collection', { libraryID: group.libraryID, parentID: c.id }); 437 yield createDataObject('collection', { libraryID: group.libraryID }); 438 yield createDataObject('collection', { libraryID: group.libraryID }); 439 440 // Group, collections, Duplicates, Unfiled, and trash 441 assert.equal(cv.rowCount, originalRowCount + 9); 442 443 // Select group 444 yield cv.selectLibrary(group.libraryID); 445 yield waitForItemsLoad(win); 446 447 var spy = sinon.spy(cv, "refresh"); 448 try { 449 yield group.eraseTx(); 450 451 assert.equal(cv.rowCount, originalRowCount); 452 // Make sure the tree wasn't refreshed 453 sinon.assert.notCalled(spy); 454 } 455 finally { 456 spy.restore(); 457 } 458 }) 459 460 it("should select a new feed", function* () { 461 var feed = yield createFeed(); 462 // Feed should be selected 463 assert.equal(cv.getSelectedLibraryID(), feed.id); 464 }); 465 466 it("shouldn't select a new feed with skipSelect: true", function* () { 467 var feed = yield createFeed({ 468 saveOptions: { 469 skipSelect: true 470 } 471 }); 472 // Library should still be selected 473 assert.equal(cv.getSelectedLibraryID(), userLibraryID); 474 }); 475 476 it("should remove deleted feed", function* () { 477 var feed = yield createFeed(); 478 yield cv.selectLibrary(feed.libraryID); 479 waitForDialog(); 480 var id = feed.treeViewID; 481 yield win.ZoteroPane.deleteSelectedCollection(); 482 assert.isFalse(cv.getRowIndexByID(id)) 483 }) 484 }); 485 486 describe("#selectItem()", function () { 487 it("should switch to library root if item isn't in collection", async function () { 488 var item = await createDataObject('item'); 489 var collection = await createDataObject('collection'); 490 await cv.selectItem(item.id); 491 await waitForItemsLoad(win); 492 assert.equal(cv.selection.currentIndex, 0); 493 assert.sameMembers(zp.itemsView.getSelectedItems(), [item]); 494 }); 495 }); 496 497 describe("#drop()", function () { 498 /** 499 * Simulate a drag and drop 500 * 501 * @param {String} type - 'item' or 'collection' 502 * @param {String|Object} targetRow - Tree row id (e.g., "L123"), or { row, orient } 503 * @param {Integer[]} collectionIDs 504 * @param {Promise} [promise] - If a promise is provided, it will be waited for and its 505 * value returned after the drag. Otherwise, an 'add' event will be waited for, and 506 * an object with 'ids' and 'extraData' will be returned. 507 */ 508 var drop = Zotero.Promise.coroutine(function* (objectType, targetRow, ids, promise, action = 'copy') { 509 if (typeof targetRow == 'string') { 510 var row = cv.getRowIndexByID(targetRow); 511 var orient = 0; 512 } 513 else { 514 var { row, orient } = targetRow; 515 } 516 517 var stub = sinon.stub(Zotero.DragDrop, "getDragTarget"); 518 stub.returns(cv.getRow(row)); 519 if (!promise) { 520 promise = waitForNotifierEvent("add", objectType); 521 } 522 yield cv.drop(row, orient, { 523 dropEffect: action, 524 effectAllowed: action, 525 mozSourceNode: win.document.getElementById(`zotero-${objectType}s-tree`).treeBoxObject.treeBody, 526 types: { 527 contains: function (type) { 528 return type == `zotero/${objectType}`; 529 } 530 }, 531 getData: function (type) { 532 if (type == `zotero/${objectType}`) { 533 return ids.join(","); 534 } 535 } 536 }); 537 538 // Add observer to wait for add 539 var result = yield promise; 540 stub.restore(); 541 return result; 542 }); 543 544 545 var canDrop = Zotero.Promise.coroutine(function* (type, targetRowID, ids) { 546 var row = cv.getRowIndexByID(targetRowID); 547 548 var stub = sinon.stub(Zotero.DragDrop, "getDragTarget"); 549 stub.returns(cv.getRow(row)); 550 var dt = { 551 dropEffect: 'copy', 552 effectAllowed: 'copy', 553 mozSourceNode: win.document.getElementById(`zotero-${type}s-tree`), 554 types: { 555 contains: function (type) { 556 return type == `zotero/${type}`; 557 } 558 }, 559 getData: function (type) { 560 if (type == `zotero/${type}`) { 561 return ids.join(","); 562 } 563 } 564 }; 565 var canDrop = cv.canDropCheck(row, 0, dt); 566 if (canDrop) { 567 canDrop = yield cv.canDropCheckAsync(row, 0, dt); 568 } 569 stub.restore(); 570 return canDrop; 571 }); 572 573 describe("with items", function () { 574 it("should add an item to a collection", function* () { 575 var collection = yield createDataObject('collection', false, { skipSelect: true }); 576 var item = yield createDataObject('item', false, { skipSelect: true }); 577 578 // Add observer to wait for collection add 579 var deferred = Zotero.Promise.defer(); 580 var observerID = Zotero.Notifier.registerObserver({ 581 notify: function (event, type, ids, extraData) { 582 if (type == 'collection-item' && event == 'add' 583 && ids[0] == collection.id + "-" + item.id) { 584 setTimeout(function () { 585 deferred.resolve(); 586 }); 587 } 588 } 589 }, 'collection-item', 'test'); 590 591 yield drop('item', 'C' + collection.id, [item.id], deferred.promise); 592 593 Zotero.Notifier.unregisterObserver(observerID); 594 595 yield cv.selectCollection(collection.id); 596 yield waitForItemsLoad(win); 597 598 var itemsView = win.ZoteroPane.itemsView 599 assert.equal(itemsView.rowCount, 1); 600 var treeRow = itemsView.getRow(0); 601 assert.equal(treeRow.ref.id, item.id); 602 }) 603 604 it("should move an item from one collection to another", function* () { 605 var collection1 = yield createDataObject('collection'); 606 yield waitForItemsLoad(win); 607 var collection2 = yield createDataObject('collection', false, { skipSelect: true }); 608 var item = yield createDataObject('item', { collections: [collection1.id] }); 609 610 // Add observer to wait for collection add 611 var deferred = Zotero.Promise.defer(); 612 var observerID = Zotero.Notifier.registerObserver({ 613 notify: function (event, type, ids, extraData) { 614 if (type == 'collection-item' && event == 'add' 615 && ids[0] == collection2.id + "-" + item.id) { 616 setTimeout(function () { 617 deferred.resolve(); 618 }); 619 } 620 } 621 }, 'collection-item', 'test'); 622 623 yield drop('item', 'C' + collection2.id, [item.id], deferred.promise, 'move'); 624 625 Zotero.Notifier.unregisterObserver(observerID); 626 627 // Source collection should be empty 628 assert.equal(zp.itemsView.rowCount, 0); 629 630 yield cv.selectCollection(collection2.id); 631 yield waitForItemsLoad(win); 632 633 // Target collection should have item 634 assert.equal(zp.itemsView.rowCount, 1); 635 var treeRow = zp.itemsView.getRow(0); 636 assert.equal(treeRow.ref.id, item.id); 637 }); 638 639 describe("My Publications", function () { 640 it("should add an item to My Publications", function* () { 641 // Remove other items in My Publications 642 var s = new Zotero.Search(); 643 s.addCondition('libraryID', 'is', Zotero.Libraries.userLibraryID); 644 s.addCondition('publications', 'true'); 645 var ids = yield s.search(); 646 yield Zotero.Items.erase(ids); 647 648 var item = yield createDataObject('item', false, { skipSelect: true }); 649 var libraryID = item.libraryID; 650 651 var stub = sinon.stub(zp, "showPublicationsWizard") 652 .returns({ 653 includeNotes: false, 654 includeFiles: false, 655 keepRights: true 656 }); 657 658 // Add observer to wait for item modification 659 var deferred = Zotero.Promise.defer(); 660 var observerID = Zotero.Notifier.registerObserver({ 661 notify: function (event, type, ids, extraData) { 662 if (type == 'item' && event == 'modify' && ids[0] == item.id) { 663 setTimeout(function () { 664 deferred.resolve(); 665 }); 666 } 667 } 668 }, 'item', 'test'); 669 670 yield drop('item', 'P' + libraryID, [item.id], deferred.promise); 671 672 Zotero.Notifier.unregisterObserver(observerID); 673 stub.restore(); 674 675 // Select publications and check for item 676 yield cv.selectByID("P" + libraryID); 677 yield waitForItemsLoad(win); 678 var itemsView = win.ZoteroPane.itemsView 679 assert.equal(itemsView.rowCount, 1); 680 var treeRow = itemsView.getRow(0); 681 assert.equal(treeRow.ref.id, item.id); 682 }); 683 684 it("should add an item with a file attachment to My Publications", function* () { 685 var item = yield createDataObject('item', false, { skipSelect: true }); 686 var attachment = yield importFileAttachment('test.png', { parentItemID: item.id }); 687 var libraryID = item.libraryID; 688 689 var stub = sinon.stub(zp, "showPublicationsWizard") 690 .returns({ 691 includeNotes: false, 692 includeFiles: true, 693 keepRights: true 694 }); 695 696 // Add observer to wait for modify 697 var deferred = Zotero.Promise.defer(); 698 var observerID = Zotero.Notifier.registerObserver({ 699 notify: function (event, type, ids, extraData) { 700 if (type == 'item' && event == 'modify' && ids[0] == item.id) { 701 setTimeout(function () { 702 deferred.resolve(); 703 }); 704 } 705 } 706 }, 'item', 'test'); 707 708 yield drop('item', 'P' + libraryID, [item.id], deferred.promise); 709 710 Zotero.Notifier.unregisterObserver(observerID); 711 stub.restore(); 712 713 assert.isTrue(item.inPublications); 714 // File attachment should be in My Publications 715 assert.isTrue(attachment.inPublications); 716 }); 717 718 it("should add an item with a linked URL attachment to My Publications", function* () { 719 var item = yield createDataObject('item', false, { skipSelect: true }); 720 var attachment = yield Zotero.Attachments.linkFromURL({ 721 parentItemID: item.id, 722 title: 'Test', 723 url: 'http://127.0.0.1/', 724 contentType: 'text/html' 725 }); 726 var libraryID = item.libraryID; 727 728 var stub = sinon.stub(zp, "showPublicationsWizard") 729 .returns({ 730 includeNotes: false, 731 includeFiles: false, 732 keepRights: true 733 }); 734 735 // Add observer to wait for modify 736 var deferred = Zotero.Promise.defer(); 737 var observerID = Zotero.Notifier.registerObserver({ 738 notify: function (event, type, ids, extraData) { 739 if (type == 'item' && event == 'modify' && ids[0] == item.id) { 740 setTimeout(function () { 741 deferred.resolve(); 742 }); 743 } 744 } 745 }, 'item', 'test'); 746 747 yield drop('item', 'P' + libraryID, [item.id], deferred.promise); 748 749 Zotero.Notifier.unregisterObserver(observerID); 750 stub.restore(); 751 752 assert.isTrue(item.inPublications); 753 // Link attachment should be in My Publications 754 assert.isTrue(attachment.inPublications); 755 }); 756 757 it("shouldn't add linked file attachment to My Publications", function* () { 758 var item = yield createDataObject('item', false, { skipSelect: true }); 759 var attachment = yield Zotero.Attachments.linkFromFile({ 760 parentItemID: item.id, 761 title: 'Test', 762 file: OS.Path.join(getTestDataDirectory().path, 'test.png'), 763 contentType: 'image/png' 764 }); 765 var libraryID = item.libraryID; 766 767 var stub = sinon.stub(zp, "showPublicationsWizard") 768 .returns({ 769 includeNotes: false, 770 includeFiles: false, 771 keepRights: true 772 }); 773 774 // Add observer to wait for modify 775 var deferred = Zotero.Promise.defer(); 776 var observerID = Zotero.Notifier.registerObserver({ 777 notify: function (event, type, ids, extraData) { 778 if (type == 'item' && event == 'modify' && ids[0] == item.id) { 779 setTimeout(function () { 780 deferred.resolve(); 781 }); 782 } 783 } 784 }, 'item', 'test'); 785 786 yield drop('item', 'P' + libraryID, [item.id], deferred.promise); 787 788 Zotero.Notifier.unregisterObserver(observerID); 789 stub.restore(); 790 791 assert.isTrue(item.inPublications); 792 // Linked URL attachment shouldn't be in My Publications 793 assert.isFalse(attachment.inPublications); 794 }); 795 }); 796 797 it("should copy an item with an attachment to a group", function* () { 798 var group = yield createGroup(); 799 800 var item = yield createDataObject('item', false, { skipSelect: true }); 801 var file = getTestDataDirectory(); 802 file.append('test.png'); 803 var attachment = yield Zotero.Attachments.importFromFile({ 804 file: file, 805 parentItemID: item.id 806 }); 807 808 var ids = (yield drop('item', 'L' + group.libraryID, [item.id])).ids; 809 810 yield cv.selectLibrary(group.libraryID); 811 yield waitForItemsLoad(win); 812 813 // Check parent 814 var itemsView = win.ZoteroPane.itemsView; 815 assert.equal(itemsView.rowCount, 1); 816 var treeRow = itemsView.getRow(0); 817 assert.equal(treeRow.ref.libraryID, group.libraryID); 818 assert.equal(treeRow.ref.id, ids[0]); 819 // New item should link back to original 820 var linked = yield item.getLinkedItem(group.libraryID); 821 assert.equal(linked.id, treeRow.ref.id); 822 823 // Check attachment 824 assert.isTrue(itemsView.isContainer(0)); 825 itemsView.toggleOpenState(0); 826 assert.equal(itemsView.rowCount, 2); 827 treeRow = itemsView.getRow(1); 828 assert.equal(treeRow.ref.id, ids[1]); 829 // New attachment should link back to original 830 linked = yield attachment.getLinkedItem(group.libraryID); 831 assert.equal(linked.id, treeRow.ref.id); 832 833 return group.eraseTx(); 834 }) 835 836 it("should not copy an item or its attachment to a group twice", function* () { 837 var group = yield getGroup(); 838 839 var itemTitle = Zotero.Utilities.randomString(); 840 var item = yield createDataObject('item', false, { skipSelect: true }); 841 var file = getTestDataDirectory(); 842 file.append('test.png'); 843 var attachment = yield Zotero.Attachments.importFromFile({ 844 file: file, 845 parentItemID: item.id 846 }); 847 var attachmentTitle = Zotero.Utilities.randomString(); 848 attachment.setField('title', attachmentTitle); 849 yield attachment.saveTx(); 850 851 yield drop('item', 'L' + group.libraryID, [item.id]); 852 assert.isFalse(yield canDrop('item', 'L' + group.libraryID, [item.id])); 853 }) 854 855 it("should remove a linked, trashed item in a group from the trash and collections", function* () { 856 var group = yield getGroup(); 857 var collection = yield createDataObject('collection', { libraryID: group.libraryID }); 858 859 var item = yield createDataObject('item', false, { skipSelect: true }); 860 yield drop('item', 'L' + group.libraryID, [item.id]); 861 862 var droppedItem = yield item.getLinkedItem(group.libraryID); 863 droppedItem.setCollections([collection.id]); 864 droppedItem.deleted = true; 865 yield droppedItem.saveTx(); 866 867 // Add observer to wait for collection add 868 var deferred = Zotero.Promise.defer(); 869 var observerID = Zotero.Notifier.registerObserver({ 870 notify: function (event, type, ids) { 871 if (event == 'refresh' && type == 'trash' && ids[0] == group.libraryID) { 872 setTimeout(function () { 873 deferred.resolve(); 874 }); 875 } 876 } 877 }, 'trash', 'test'); 878 yield drop('item', 'L' + group.libraryID, [item.id], deferred.promise); 879 Zotero.Notifier.unregisterObserver(observerID); 880 881 assert.isFalse(droppedItem.deleted); 882 // Should be removed from collections when removed from trash 883 assert.lengthOf(droppedItem.getCollections(), 0); 884 }) 885 }) 886 887 888 describe("with collections", function () { 889 it("should make a subcollection top-level", function* () { 890 var collection1 = yield createDataObject('collection', { name: "A" }, { skipSelect: true }); 891 var collection2 = yield createDataObject('collection', { name: "C" }, { skipSelect: true }); 892 var collection3 = yield createDataObject('collection', { name: "D" }, { skipSelect: true }); 893 var collection4 = yield createDataObject('collection', { name: "B", parentKey: collection2.key }); 894 895 var colIndex1 = cv.getRowIndexByID('C' + collection1.id); 896 var colIndex2 = cv.getRowIndexByID('C' + collection2.id); 897 var colIndex3 = cv.getRowIndexByID('C' + collection3.id); 898 var colIndex4 = cv.getRowIndexByID('C' + collection4.id); 899 900 // Add observer to wait for collection add 901 var deferred = Zotero.Promise.defer(); 902 var observerID = Zotero.Notifier.registerObserver({ 903 notify: function (event, type, ids, extraData) { 904 if (type == 'collection' && event == 'modify' && ids[0] == collection4.id) { 905 setTimeout(function () { 906 deferred.resolve(); 907 }, 50); 908 } 909 } 910 }, 'collection', 'test'); 911 912 yield drop( 913 'collection', 914 { 915 row: 0, 916 orient: 1 917 }, 918 [collection4.id], 919 deferred.promise 920 ); 921 922 Zotero.Notifier.unregisterObserver(observerID); 923 924 var newColIndex1 = cv.getRowIndexByID('C' + collection1.id); 925 var newColIndex2 = cv.getRowIndexByID('C' + collection2.id); 926 var newColIndex3 = cv.getRowIndexByID('C' + collection3.id); 927 var newColIndex4 = cv.getRowIndexByID('C' + collection4.id); 928 929 assert.equal(newColIndex1, colIndex1); 930 assert.isBelow(newColIndex4, newColIndex2); 931 assert.isBelow(newColIndex2, newColIndex3); 932 assert.equal(cv.getRow(newColIndex4).level, cv.getRow(newColIndex1).level); 933 }) 934 935 it("should move a subcollection and its subcollection down under another collection", function* () { 936 var collectionA = yield createDataObject('collection', { name: "A" }, { skipSelect: true }); 937 var collectionB = yield createDataObject('collection', { name: "B", parentKey: collectionA.key }); 938 var collectionC = yield createDataObject('collection', { name: "C", parentKey: collectionB.key }); 939 var collectionD = yield createDataObject('collection', { name: "D" }, { skipSelect: true }); 940 var collectionE = yield createDataObject('collection', { name: "E" }, { skipSelect: true }); 941 var collectionF = yield createDataObject('collection', { name: "F" }, { skipSelect: true }); 942 var collectionG = yield createDataObject('collection', { name: "G", parentKey: collectionD.key }); 943 var collectionH = yield createDataObject('collection', { name: "H", parentKey: collectionG.key }); 944 945 var colIndexA = cv.getRowIndexByID('C' + collectionA.id); 946 var colIndexB = cv.getRowIndexByID('C' + collectionB.id); 947 var colIndexC = cv.getRowIndexByID('C' + collectionC.id); 948 var colIndexD = cv.getRowIndexByID('C' + collectionD.id); 949 var colIndexE = cv.getRowIndexByID('C' + collectionE.id); 950 var colIndexF = cv.getRowIndexByID('C' + collectionF.id); 951 var colIndexG = cv.getRowIndexByID('C' + collectionG.id); 952 var colIndexH = cv.getRowIndexByID('C' + collectionH.id); 953 954 yield cv.selectCollection(collectionG.id); 955 956 // Add observer to wait for collection add 957 var deferred = Zotero.Promise.defer(); 958 var observerID = Zotero.Notifier.registerObserver({ 959 notify: function (event, type, ids, extraData) { 960 if (type == 'collection' && event == 'modify' && ids[0] == collectionG.id) { 961 setTimeout(function () { 962 deferred.resolve(); 963 }, 50); 964 } 965 } 966 }, 'collection', 'test'); 967 968 yield drop( 969 'collection', 970 { 971 row: colIndexE, 972 orient: 0 973 }, 974 [collectionG.id], 975 deferred.promise 976 ); 977 978 Zotero.Notifier.unregisterObserver(observerID); 979 980 var newColIndexA = cv.getRowIndexByID('C' + collectionA.id); 981 var newColIndexB = cv.getRowIndexByID('C' + collectionB.id); 982 var newColIndexC = cv.getRowIndexByID('C' + collectionC.id); 983 var newColIndexD = cv.getRowIndexByID('C' + collectionD.id); 984 var newColIndexE = cv.getRowIndexByID('C' + collectionE.id); 985 var newColIndexF = cv.getRowIndexByID('C' + collectionF.id); 986 var newColIndexG = cv.getRowIndexByID('C' + collectionG.id); 987 var newColIndexH = cv.getRowIndexByID('C' + collectionH.id); 988 989 assert.isFalse(cv.isContainerOpen(newColIndexD)); 990 assert.isTrue(cv.isContainerEmpty(newColIndexD)); 991 assert.isTrue(cv.isContainerOpen(newColIndexE)); 992 assert.isFalse(cv.isContainerEmpty(newColIndexE)); 993 assert.equal(newColIndexE, newColIndexG - 1); 994 assert.equal(newColIndexG, newColIndexH - 1); 995 996 // TODO: Check deeper subcollection open states 997 }) 998 999 it("should move a subcollection and its subcollection up under another collection", function* () { 1000 var collectionA = yield createDataObject('collection', { name: "A" }, { skipSelect: true }); 1001 var collectionB = yield createDataObject('collection', { name: "B", parentKey: collectionA.key }); 1002 var collectionC = yield createDataObject('collection', { name: "C", parentKey: collectionB.key }); 1003 var collectionD = yield createDataObject('collection', { name: "D" }, { skipSelect: true }); 1004 var collectionE = yield createDataObject('collection', { name: "E" }, { skipSelect: true }); 1005 var collectionF = yield createDataObject('collection', { name: "F" }, { skipSelect: true }); 1006 var collectionG = yield createDataObject('collection', { name: "G", parentKey: collectionE.key }); 1007 var collectionH = yield createDataObject('collection', { name: "H", parentKey: collectionG.key }); 1008 1009 var colIndexA = cv.getRowIndexByID('C' + collectionA.id); 1010 var colIndexB = cv.getRowIndexByID('C' + collectionB.id); 1011 var colIndexC = cv.getRowIndexByID('C' + collectionC.id); 1012 var colIndexD = cv.getRowIndexByID('C' + collectionD.id); 1013 var colIndexE = cv.getRowIndexByID('C' + collectionE.id); 1014 var colIndexF = cv.getRowIndexByID('C' + collectionF.id); 1015 var colIndexG = cv.getRowIndexByID('C' + collectionG.id); 1016 var colIndexH = cv.getRowIndexByID('C' + collectionH.id); 1017 1018 yield cv.selectCollection(collectionG.id); 1019 1020 // Add observer to wait for collection add 1021 var deferred = Zotero.Promise.defer(); 1022 var observerID = Zotero.Notifier.registerObserver({ 1023 notify: function (event, type, ids, extraData) { 1024 if (type == 'collection' && event == 'modify' && ids[0] == collectionG.id) { 1025 setTimeout(function () { 1026 deferred.resolve(); 1027 }, 50); 1028 } 1029 } 1030 }, 'collection', 'test'); 1031 1032 yield drop( 1033 'collection', 1034 { 1035 row: colIndexD, 1036 orient: 0 1037 }, 1038 [collectionG.id], 1039 deferred.promise 1040 ); 1041 1042 Zotero.Notifier.unregisterObserver(observerID); 1043 1044 var newColIndexA = cv.getRowIndexByID('C' + collectionA.id); 1045 var newColIndexB = cv.getRowIndexByID('C' + collectionB.id); 1046 var newColIndexC = cv.getRowIndexByID('C' + collectionC.id); 1047 var newColIndexD = cv.getRowIndexByID('C' + collectionD.id); 1048 var newColIndexE = cv.getRowIndexByID('C' + collectionE.id); 1049 var newColIndexF = cv.getRowIndexByID('C' + collectionF.id); 1050 var newColIndexG = cv.getRowIndexByID('C' + collectionG.id); 1051 var newColIndexH = cv.getRowIndexByID('C' + collectionH.id); 1052 1053 assert.isFalse(cv.isContainerOpen(newColIndexE)); 1054 assert.isTrue(cv.isContainerEmpty(newColIndexE)); 1055 assert.isTrue(cv.isContainerOpen(newColIndexD)); 1056 assert.isFalse(cv.isContainerEmpty(newColIndexD)); 1057 assert.equal(newColIndexD, newColIndexG - 1); 1058 assert.equal(newColIndexG, newColIndexH - 1); 1059 1060 // TODO: Check deeper subcollection open states 1061 }) 1062 }) 1063 1064 1065 describe("with feed items", function () { 1066 it('should add a translated feed item recovered from an URL', function* (){ 1067 var feed = yield createFeed(); 1068 var collection = yield createDataObject('collection', false, { skipSelect: true }); 1069 var url = getTestDataUrl('metadata/journalArticle-single.html'); 1070 var feedItem = yield createDataObject('feedItem', {libraryID: feed.libraryID}, { skipSelect: true }); 1071 feedItem.setField('url', url); 1072 yield feedItem.saveTx(); 1073 var translateFn = sinon.spy(feedItem, 'translate'); 1074 1075 // Add observer to wait for collection add 1076 var deferred = Zotero.Promise.defer(); 1077 var itemIds; 1078 1079 var ids = (yield drop('item', 'C' + collection.id, [feedItem.id])).ids; 1080 1081 // Check that the translated item was the one that was created after drag 1082 var item; 1083 yield translateFn.returnValues[0].then(function(i) { 1084 item = i; 1085 assert.equal(item.id, ids[0]); 1086 }); 1087 1088 yield cv.selectCollection(collection.id); 1089 yield waitForItemsLoad(win); 1090 1091 var itemsView = win.ZoteroPane.itemsView; 1092 assert.equal(itemsView.rowCount, 1); 1093 var treeRow = itemsView.getRow(0); 1094 assert.equal(treeRow.ref.id, item.id); 1095 }) 1096 }) 1097 }) 1098 })