itemTreeViewTest.js (36340B)
1 "use strict"; 2 3 describe("Zotero.ItemTreeView", function() { 4 var win, zp, cv, itemsView, existingItemID; 5 6 // Load Zotero pane and select library 7 before(function* () { 8 win = yield loadZoteroPane(); 9 zp = win.ZoteroPane; 10 cv = zp.collectionsView; 11 12 var item = yield createDataObject('item', { setTitle: true }); 13 existingItemID = item.id; 14 }); 15 beforeEach(function* () { 16 yield selectLibrary(win); 17 itemsView = zp.itemsView; 18 }) 19 after(function () { 20 win.close(); 21 }); 22 23 it("shouldn't show items in trash in library root", function* () { 24 var item = yield createDataObject('item', { title: "foo" }); 25 var itemID = item.id; 26 item.deleted = true; 27 yield item.saveTx(); 28 assert.isFalse(itemsView.getRowIndexByID(itemID)); 29 }) 30 31 describe("#selectItem()", function () { 32 /** 33 * Make sure that selectItem() doesn't hang if the pane's item-select handler is never 34 * triggered due to the item already being selected 35 */ 36 it("should return if item is already selected", function* () { 37 yield itemsView.selectItem(existingItemID); 38 var selected = itemsView.getSelectedItems(true); 39 assert.lengthOf(selected, 1); 40 assert.equal(selected[0], existingItemID); 41 yield itemsView.selectItem(existingItemID); 42 selected = itemsView.getSelectedItems(true); 43 assert.lengthOf(selected, 1); 44 assert.equal(selected[0], existingItemID); 45 }); 46 }) 47 48 describe("#getCellText()", function () { 49 it("should return new value after edit", function* () { 50 var str = Zotero.Utilities.randomString(); 51 var item = yield createDataObject('item', { title: str }); 52 var row = itemsView.getRowIndexByID(item.id); 53 assert.equal(itemsView.getCellText(row, { id: 'zotero-items-column-title' }), str); 54 yield modifyDataObject(item); 55 assert.notEqual(itemsView.getCellText(row, { id: 'zotero-items-column-title' }), str); 56 }) 57 }) 58 59 describe("#notify()", function () { 60 beforeEach(function () { 61 sinon.spy(win.ZoteroPane, "itemSelected"); 62 }) 63 64 afterEach(function () { 65 win.ZoteroPane.itemSelected.restore(); 66 }) 67 68 it("should select a new item", function* () { 69 itemsView.selection.clearSelection(); 70 assert.lengthOf(itemsView.getSelectedItems(), 0); 71 72 assert.equal(win.ZoteroPane.itemSelected.callCount, 1); 73 74 // Create item 75 var item = new Zotero.Item('book'); 76 var id = yield item.saveTx(); 77 78 // New item should be selected 79 var selected = itemsView.getSelectedItems(); 80 assert.lengthOf(selected, 1); 81 assert.equal(selected[0].id, id); 82 83 // Item should have been selected once 84 assert.equal(win.ZoteroPane.itemSelected.callCount, 2); 85 assert.ok(win.ZoteroPane.itemSelected.returnValues[1].value()); 86 }); 87 88 it("shouldn't select a new item if skipNotifier is passed", function* () { 89 // Select existing item 90 yield itemsView.selectItem(existingItemID); 91 var selected = itemsView.getSelectedItems(true); 92 assert.lengthOf(selected, 1); 93 assert.equal(selected[0], existingItemID); 94 95 // Reset call count on spy 96 win.ZoteroPane.itemSelected.reset(); 97 98 // Create item with skipNotifier flag 99 var item = new Zotero.Item('book'); 100 var id = yield item.saveTx({ 101 skipNotifier: true 102 }); 103 104 // No select events should have occurred 105 assert.equal(win.ZoteroPane.itemSelected.callCount, 0); 106 107 // Existing item should still be selected 108 selected = itemsView.getSelectedItems(true); 109 assert.lengthOf(selected, 1); 110 assert.equal(selected[0], existingItemID); 111 }); 112 113 it("shouldn't select a new item if skipSelect is passed", function* () { 114 // Select existing item 115 yield itemsView.selectItem(existingItemID); 116 var selected = itemsView.getSelectedItems(true); 117 assert.lengthOf(selected, 1); 118 assert.equal(selected[0], existingItemID); 119 120 // Reset call count on spy 121 win.ZoteroPane.itemSelected.reset(); 122 123 // Create item with skipSelect flag 124 var item = new Zotero.Item('book'); 125 var id = yield item.saveTx({ 126 skipSelect: true 127 }); 128 129 // itemSelected should have been called once (from 'selectEventsSuppressed = false' 130 // in notify()) as a no-op 131 assert.equal(win.ZoteroPane.itemSelected.callCount, 1); 132 assert.isFalse(win.ZoteroPane.itemSelected.returnValues[0].value()); 133 134 // Existing item should still be selected 135 selected = itemsView.getSelectedItems(true); 136 assert.lengthOf(selected, 1); 137 assert.equal(selected[0], existingItemID); 138 }); 139 140 it("should clear search and select new item if non-matching quick search is active", async function () { 141 await createDataObject('item'); 142 143 var quicksearch = win.document.getElementById('zotero-tb-search'); 144 quicksearch.value = Zotero.randomString(); 145 quicksearch.doCommand(); 146 await itemsView._refreshPromise; 147 148 assert.equal(itemsView.rowCount, 0); 149 150 // Create item 151 var item = await createDataObject('item'); 152 153 assert.isAbove(itemsView.rowCount, 0); 154 assert.equal(quicksearch.value, ''); 155 156 // New item should be selected 157 var selected = itemsView.getSelectedItems(); 158 assert.lengthOf(selected, 1); 159 assert.equal(selected[0].id, item.id); 160 }); 161 162 it("shouldn't clear quicksearch if skipSelect is passed", function* () { 163 var searchString = Zotero.Items.get(existingItemID).getField('title'); 164 165 yield createDataObject('item'); 166 167 var quicksearch = win.document.getElementById('zotero-tb-search'); 168 quicksearch.value = searchString; 169 quicksearch.doCommand(); 170 yield itemsView._refreshPromise; 171 172 assert.equal(itemsView.rowCount, 1); 173 174 // Create item with skipSelect flag 175 var item = new Zotero.Item('book'); 176 var ran = Zotero.Utilities.randomString(); 177 item.setField('title', ran); 178 var id = yield item.saveTx({ 179 skipSelect: true 180 }); 181 182 assert.equal(itemsView.rowCount, 1); 183 assert.equal(quicksearch.value, searchString); 184 185 // Clear search 186 quicksearch.value = ""; 187 quicksearch.doCommand(); 188 yield itemsView._refreshPromise; 189 }); 190 191 it("shouldn't change selection outside of trash if new trashed item is created with skipSelect", function* () { 192 yield selectLibrary(win); 193 yield waitForItemsLoad(win); 194 195 itemsView.selection.clearSelection(); 196 197 var item = createUnsavedDataObject('item'); 198 item.deleted = true; 199 var id = yield item.saveTx({ 200 skipSelect: true 201 }); 202 203 // Nothing should be selected 204 var selected = itemsView.getSelectedItems(true); 205 assert.lengthOf(selected, 0); 206 }) 207 208 it("shouldn't select a modified item", function* () { 209 // Create item 210 var item = new Zotero.Item('book'); 211 var id = yield item.saveTx(); 212 213 itemsView.selection.clearSelection(); 214 assert.lengthOf(itemsView.getSelectedItems(), 0); 215 // Reset call count on spy 216 win.ZoteroPane.itemSelected.reset(); 217 218 // Modify item 219 item.setField('title', 'no select on modify'); 220 yield item.saveTx(); 221 222 // itemSelected should have been called once (from 'selectEventsSuppressed = false' 223 // in notify()) as a no-op 224 assert.equal(win.ZoteroPane.itemSelected.callCount, 1); 225 assert.isFalse(win.ZoteroPane.itemSelected.returnValues[0].value()); 226 227 // Modified item should not be selected 228 assert.lengthOf(itemsView.getSelectedItems(), 0); 229 }); 230 231 it("should maintain selection on a selected modified item", function* () { 232 // Create item 233 var item = new Zotero.Item('book'); 234 var id = yield item.saveTx(); 235 236 yield itemsView.selectItem(id); 237 var selected = itemsView.getSelectedItems(true); 238 assert.lengthOf(selected, 1); 239 assert.equal(selected[0], id); 240 241 // Reset call count on spy 242 win.ZoteroPane.itemSelected.reset(); 243 244 // Modify item 245 item.setField('title', 'maintain selection on modify'); 246 yield item.saveTx(); 247 248 // itemSelected should have been called once (from 'selectEventsSuppressed = false' 249 // in notify()) as a no-op 250 assert.equal(win.ZoteroPane.itemSelected.callCount, 1); 251 assert.isFalse(win.ZoteroPane.itemSelected.returnValues[0].value()); 252 253 // Modified item should still be selected 254 selected = itemsView.getSelectedItems(true); 255 assert.lengthOf(selected, 1); 256 assert.equal(selected[0], id); 257 }); 258 259 it("should reselect the same row when an item is removed", function* () { 260 var collection = yield createDataObject('collection'); 261 yield waitForItemsLoad(win); 262 itemsView = zp.itemsView; 263 264 var items = []; 265 var num = 6; 266 for (let i = 0; i < num; i++) { 267 let item = createUnsavedDataObject('item', { title: "" + i }); 268 item.addToCollection(collection.id); 269 yield item.saveTx(); 270 items.push(item); 271 } 272 assert.equal(itemsView.rowCount, num); 273 274 // Select the third item in the list 275 itemsView.selection.select(2); 276 277 // Remove item 278 var treeRow = itemsView.getRow(2); 279 yield Zotero.DB.executeTransaction(function* () { 280 yield collection.removeItems([treeRow.ref.id]); 281 }.bind(this)); 282 283 // Selection should stay on third row 284 assert.equal(itemsView.selection.currentIndex, 2); 285 286 // Delete item 287 var treeRow = itemsView.getRow(2); 288 yield treeRow.ref.eraseTx(); 289 290 // Selection should stay on third row 291 assert.equal(itemsView.selection.currentIndex, 2); 292 293 yield Zotero.Items.erase(items.map(item => item.id)); 294 }); 295 296 it("shouldn't select sibling on attachment erase if attachment wasn't selected", function* () { 297 var item = yield createDataObject('item'); 298 var att1 = yield importFileAttachment('test.png', { title: 'A', parentItemID: item.id }); 299 var att2 = yield importFileAttachment('test.png', { title: 'B', parentItemID: item.id }); 300 yield zp.itemsView.selectItem(att2.id); // expand 301 yield zp.itemsView.selectItem(item.id); 302 yield att1.eraseTx(); 303 assert.sameMembers(zp.itemsView.getSelectedItems(true), [item.id]); 304 }); 305 306 it("should keep first visible item in view when other items are added with skipSelect and nothing in view is selected", function* () { 307 var collection = yield createDataObject('collection'); 308 yield waitForItemsLoad(win); 309 itemsView = zp.itemsView; 310 311 var treebox = itemsView._treebox; 312 var numVisibleRows = treebox.getPageLength(); 313 314 // Get a numeric string left-padded with zeroes 315 function getTitle(i, max) { 316 return new String(new Array(max + 1).join(0) + i).slice(-1 * max); 317 } 318 319 var num = numVisibleRows + 10; 320 yield Zotero.DB.executeTransaction(function* () { 321 for (let i = 0; i < num; i++) { 322 let title = getTitle(i, num); 323 let item = createUnsavedDataObject('item', { title }); 324 item.addToCollection(collection.id); 325 yield item.save(); 326 } 327 }.bind(this)); 328 329 // Scroll halfway 330 treebox.scrollToRow(Math.round(num / 2) - Math.round(numVisibleRows / 2)); 331 332 var firstVisibleItemID = itemsView.getRow(treebox.getFirstVisibleRow()).ref.id; 333 334 // Add one item at the beginning 335 var item = createUnsavedDataObject( 336 'item', { title: getTitle(0, num), collections: [collection.id] } 337 ); 338 yield item.saveTx({ 339 skipSelect: true 340 }); 341 // Then add a few more in a transaction 342 yield Zotero.DB.executeTransaction(function* () { 343 for (let i = 0; i < 3; i++) { 344 var item = createUnsavedDataObject( 345 'item', { title: getTitle(0, num), collections: [collection.id] } 346 ); 347 yield item.save({ 348 skipSelect: true 349 }); 350 } 351 }.bind(this)); 352 353 // Make sure the same item is still in the first visible row 354 assert.equal(itemsView.getRow(treebox.getFirstVisibleRow()).ref.id, firstVisibleItemID); 355 }); 356 357 it("should keep first visible selected item in position when other items are added with skipSelect", function* () { 358 var collection = yield createDataObject('collection'); 359 yield waitForItemsLoad(win); 360 itemsView = zp.itemsView; 361 362 var treebox = itemsView._treebox; 363 var numVisibleRows = treebox.getPageLength(); 364 365 // Get a numeric string left-padded with zeroes 366 function getTitle(i, max) { 367 return new String(new Array(max + 1).join(0) + i).slice(-1 * max); 368 } 369 370 var num = numVisibleRows + 10; 371 yield Zotero.DB.executeTransaction(function* () { 372 for (let i = 0; i < num; i++) { 373 let title = getTitle(i, num); 374 let item = createUnsavedDataObject('item', { title }); 375 item.addToCollection(collection.id); 376 yield item.save(); 377 } 378 }.bind(this)); 379 380 // Scroll halfway 381 treebox.scrollToRow(Math.round(num / 2) - Math.round(numVisibleRows / 2)); 382 383 // Select an item 384 itemsView.selection.select(Math.round(num / 2)); 385 var selectedItem = itemsView.getSelectedItems()[0]; 386 var offset = itemsView.getRowIndexByID(selectedItem.treeViewID) - treebox.getFirstVisibleRow(); 387 388 // Add one item at the beginning 389 var item = createUnsavedDataObject( 390 'item', { title: getTitle(0, num), collections: [collection.id] } 391 ); 392 yield item.saveTx({ 393 skipSelect: true 394 }); 395 // Then add a few more in a transaction 396 yield Zotero.DB.executeTransaction(function* () { 397 for (let i = 0; i < 3; i++) { 398 var item = createUnsavedDataObject( 399 'item', { title: getTitle(0, num), collections: [collection.id] } 400 ); 401 yield item.save({ 402 skipSelect: true 403 }); 404 } 405 }.bind(this)); 406 407 // Make sure the selected item is still at the same position 408 assert.equal(itemsView.getSelectedItems()[0], selectedItem); 409 var newOffset = itemsView.getRowIndexByID(selectedItem.treeViewID) - treebox.getFirstVisibleRow(); 410 assert.equal(newOffset, offset); 411 }); 412 413 it("shouldn't scroll items list if at top when other items are added with skipSelect", function* () { 414 var collection = yield createDataObject('collection'); 415 yield waitForItemsLoad(win); 416 itemsView = zp.itemsView; 417 418 var treebox = itemsView._treebox; 419 var numVisibleRows = treebox.getPageLength(); 420 421 // Get a numeric string left-padded with zeroes 422 function getTitle(i, max) { 423 return new String(new Array(max + 1).join(0) + i).slice(-1 * max); 424 } 425 426 var num = numVisibleRows + 10; 427 yield Zotero.DB.executeTransaction(function* () { 428 // Start at "*1" so we can add items before 429 for (let i = 1; i < num; i++) { 430 let title = getTitle(i, num); 431 let item = createUnsavedDataObject('item', { title }); 432 item.addToCollection(collection.id); 433 yield item.save(); 434 } 435 }.bind(this)); 436 437 // Scroll to top 438 treebox.scrollToRow(0); 439 440 // Add one item at the beginning 441 var item = createUnsavedDataObject( 442 'item', { title: getTitle(0, num), collections: [collection.id] } 443 ); 444 yield item.saveTx({ 445 skipSelect: true 446 }); 447 // Then add a few more in a transaction 448 yield Zotero.DB.executeTransaction(function* () { 449 for (let i = 0; i < 3; i++) { 450 var item = createUnsavedDataObject( 451 'item', { title: getTitle(0, num), collections: [collection.id] } 452 ); 453 yield item.save({ 454 skipSelect: true 455 }); 456 } 457 }.bind(this)); 458 459 // Make sure the first row is still at the top 460 assert.equal(treebox.getFirstVisibleRow(), 0); 461 }); 462 463 it("should update search results when items are added", function* () { 464 var search = yield createDataObject('search'); 465 var title = search.getConditions()[0].value; 466 467 yield waitForItemsLoad(win); 468 assert.equal(zp.itemsView.rowCount, 0); 469 470 // Add an item matching search 471 var item = yield createDataObject('item', { title }); 472 473 yield waitForItemsLoad(win); 474 assert.equal(zp.itemsView.rowCount, 1); 475 assert.equal(zp.itemsView.getRowIndexByID(item.id), 0); 476 }); 477 478 it("should re-sort search results when an item is modified", function* () { 479 var search = yield createDataObject('search'); 480 itemsView = zp.itemsView; 481 var title = search.getConditions()[0].value; 482 483 var item1 = yield createDataObject('item', { title: title + " 1" }); 484 var item2 = yield createDataObject('item', { title: title + " 3" }); 485 var item3 = yield createDataObject('item', { title: title + " 5" }); 486 var item4 = yield createDataObject('item', { title: title + " 7" }); 487 488 var col = itemsView._treebox.columns.getNamedColumn('zotero-items-column-title'); 489 col.element.click(); 490 if (col.element.getAttribute('sortDirection') == 'ascending') { 491 col.element.click(); 492 } 493 494 // Check initial sort order 495 assert.equal(itemsView.getRow(0).ref.getField('title'), title + " 7"); 496 assert.equal(itemsView.getRow(3).ref.getField('title'), title + " 1"); 497 498 // Set first row to title that should be sorted in the middle 499 itemsView.getRow(0).ref.setField('title', title + " 4"); 500 yield itemsView.getRow(0).ref.saveTx(); 501 502 assert.equal(itemsView.getRow(0).ref.getField('title'), title + " 5"); 503 assert.equal(itemsView.getRow(1).ref.getField('title'), title + " 4"); 504 assert.equal(itemsView.getRow(3).ref.getField('title'), title + " 1"); 505 }); 506 507 it("should update search results when search conditions are changed", function* () { 508 var search = createUnsavedDataObject('search'); 509 var title1 = Zotero.Utilities.randomString(); 510 var title2 = Zotero.Utilities.randomString(); 511 search.fromJSON({ 512 name: "Test", 513 conditions: [ 514 { 515 condition: "title", 516 operator: "is", 517 value: title1 518 } 519 ] 520 }); 521 yield search.saveTx(); 522 523 yield waitForItemsLoad(win); 524 525 // Add an item that doesn't match search 526 var item = yield createDataObject('item', { title: title2 }); 527 yield waitForItemsLoad(win); 528 assert.equal(zp.itemsView.rowCount, 0); 529 530 // Modify conditions to match item 531 search.removeCondition(0); 532 search.addCondition("title", "is", title2); 533 yield search.saveTx(); 534 535 yield waitForItemsLoad(win); 536 537 assert.equal(zp.itemsView.rowCount, 1); 538 }); 539 540 it("should remove items from Unfiled Items when added to a collection", function* () { 541 var userLibraryID = Zotero.Libraries.userLibraryID; 542 var collection = yield createDataObject('collection'); 543 var item = yield createDataObject('item', { title: "Unfiled Item" }); 544 yield zp.setVirtual(userLibraryID, 'unfiled', true); 545 var selected = yield cv.selectByID("U" + userLibraryID); 546 assert.ok(selected); 547 yield waitForItemsLoad(win); 548 assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); 549 yield Zotero.DB.executeTransaction(function* () { 550 yield collection.addItem(item.id); 551 }); 552 assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); 553 }); 554 555 describe("Trash", function () { 556 it("should remove untrashed parent item when last trashed child is deleted", function* () { 557 var userLibraryID = Zotero.Libraries.userLibraryID; 558 var item = yield createDataObject('item'); 559 var note = yield createDataObject( 560 'item', { itemType: 'note', parentID: item.id, deleted: true } 561 ); 562 yield cv.selectByID("T" + userLibraryID); 563 yield waitForItemsLoad(win); 564 assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); 565 var promise = waitForDialog(); 566 yield zp.emptyTrash(); 567 yield promise; 568 assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); 569 }); 570 }); 571 572 describe("My Publications", function () { 573 before(function* () { 574 var libraryID = Zotero.Libraries.userLibraryID; 575 576 var s = new Zotero.Search; 577 s.libraryID = libraryID; 578 s.addCondition('publications', 'true'); 579 var ids = yield s.search(); 580 581 yield Zotero.Items.erase(ids); 582 583 yield zp.collectionsView.selectByID("P" + libraryID); 584 yield waitForItemsLoad(win); 585 586 // Make sure we're showing the intro text 587 var deck = win.document.getElementById('zotero-items-pane-content'); 588 assert.equal(deck.selectedIndex, 1); 589 }); 590 591 it("should replace My Publications intro text with items list on item add", function* () { 592 var item = yield createDataObject('item'); 593 594 yield zp.collectionsView.selectByID("P" + item.libraryID); 595 yield waitForItemsLoad(win); 596 var iv = zp.itemsView; 597 598 item.inPublications = true; 599 yield item.saveTx(); 600 601 var deck = win.document.getElementById('zotero-items-pane-content'); 602 assert.equal(deck.selectedIndex, 0); 603 604 assert.isNumber(iv.getRowIndexByID(item.id)); 605 }); 606 607 it("should add new item to My Publications items list", function* () { 608 var item1 = createUnsavedDataObject('item'); 609 item1.inPublications = true; 610 yield item1.saveTx(); 611 612 yield zp.collectionsView.selectByID("P" + item1.libraryID); 613 yield waitForItemsLoad(win); 614 var iv = zp.itemsView; 615 616 var deck = win.document.getElementById('zotero-items-pane-content'); 617 assert.equal(deck.selectedIndex, 0); 618 619 var item2 = createUnsavedDataObject('item'); 620 item2.inPublications = true; 621 yield item2.saveTx(); 622 623 assert.isNumber(iv.getRowIndexByID(item2.id)); 624 }); 625 626 it("should add modified item to My Publications items list", function* () { 627 var item1 = createUnsavedDataObject('item'); 628 item1.inPublications = true; 629 yield item1.saveTx(); 630 var item2 = yield createDataObject('item'); 631 632 yield zp.collectionsView.selectByID("P" + item1.libraryID); 633 yield waitForItemsLoad(win); 634 var iv = zp.itemsView; 635 636 var deck = win.document.getElementById('zotero-items-pane-content'); 637 assert.equal(deck.selectedIndex, 0); 638 639 assert.isFalse(iv.getRowIndexByID(item2.id)); 640 641 item2.inPublications = true; 642 yield item2.saveTx(); 643 644 assert.isNumber(iv.getRowIndexByID(item2.id)); 645 }); 646 647 it("should show Show/Hide button for imported file attachment", function* () { 648 var item = yield createDataObject('item', { inPublications: true }); 649 var attachment = yield importFileAttachment('test.png', { parentItemID: item.id }); 650 651 yield zp.collectionsView.selectByID("P" + item.libraryID); 652 yield waitForItemsLoad(win); 653 var iv = zp.itemsView; 654 655 yield iv.selectItem(attachment.id); 656 657 var box = win.document.getElementById('zotero-item-pane-top-buttons-my-publications'); 658 assert.isFalse(box.hidden); 659 }); 660 661 it("shouldn't show Show/Hide button for linked file attachment", function* () { 662 var item = yield createDataObject('item', { inPublications: true }); 663 var attachment = yield Zotero.Attachments.linkFromFile({ 664 file: OS.Path.join(getTestDataDirectory().path, 'test.png'), 665 parentItemID: item.id 666 }); 667 668 yield zp.collectionsView.selectByID("P" + item.libraryID); 669 yield waitForItemsLoad(win); 670 var iv = zp.itemsView; 671 672 yield iv.selectItem(attachment.id); 673 674 var box = win.document.getElementById('zotero-item-pane-top-buttons-my-publications'); 675 assert.isTrue(box.hidden); 676 }); 677 }); 678 }) 679 680 681 describe("#drop()", function () { 682 var httpd; 683 var port = 16213; 684 var baseURL = `http://localhost:${port}/`; 685 var pdfFilename = "test.pdf"; 686 var pdfURL = baseURL + pdfFilename; 687 var pdfPath; 688 689 // Serve a PDF to test URL dragging 690 before(function () { 691 Components.utils.import("resource://zotero-unit/httpd.js"); 692 httpd = new HttpServer(); 693 httpd.start(port); 694 var file = getTestDataDirectory(); 695 file.append(pdfFilename); 696 pdfPath = file.path; 697 httpd.registerFile("/" + pdfFilename, file); 698 }); 699 700 beforeEach(() => { 701 // Don't run recognize on every file 702 Zotero.Prefs.set('autoRecognizeFiles', false); 703 Zotero.Prefs.clear('autoRenameFiles'); 704 }); 705 706 after(function* () { 707 var defer = new Zotero.Promise.defer(); 708 httpd.stop(() => defer.resolve()); 709 yield defer.promise; 710 711 Zotero.Prefs.clear('autoRecognizeFiles'); 712 Zotero.Prefs.clear('autoRenameFiles'); 713 }); 714 715 it("should move a child item from one item to another", function* () { 716 var collection = yield createDataObject('collection'); 717 yield waitForItemsLoad(win); 718 var item1 = yield createDataObject('item', { title: "A", collections: [collection.id] }); 719 var item2 = yield createDataObject('item', { title: "B", collections: [collection.id] }); 720 var item3 = yield createDataObject('item', { itemType: 'note', parentID: item1.id }); 721 722 let view = zp.itemsView; 723 yield view.selectItem(item3.id, true); 724 725 var promise = view.waitForSelect(); 726 727 view.drop(view.getRowIndexByID(item2.id), 0, { 728 dropEffect: 'copy', 729 effectAllowed: 'copy', 730 types: { 731 contains: function (type) { 732 return type == 'zotero/item'; 733 } 734 }, 735 getData: function (type) { 736 if (type == 'zotero/item') { 737 return item3.id + ""; 738 } 739 }, 740 mozItemCount: 1 741 }) 742 743 yield promise; 744 745 // Old parent should be empty 746 assert.isFalse(view.isContainerOpen(view.getRowIndexByID(item1.id))); 747 assert.isTrue(view.isContainerEmpty(view.getRowIndexByID(item1.id))); 748 749 // New parent should be open 750 assert.isTrue(view.isContainerOpen(view.getRowIndexByID(item2.id))); 751 assert.isFalse(view.isContainerEmpty(view.getRowIndexByID(item2.id))); 752 }); 753 754 it("should move a child item from last item in list to another", function* () { 755 var collection = yield createDataObject('collection'); 756 yield waitForItemsLoad(win); 757 var item1 = yield createDataObject('item', { title: "A", collections: [collection.id] }); 758 var item2 = yield createDataObject('item', { title: "B", collections: [collection.id] }); 759 var item3 = yield createDataObject('item', { itemType: 'note', parentID: item2.id }); 760 761 let view = zp.itemsView; 762 yield view.selectItem(item3.id, true); 763 764 var promise = view.waitForSelect(); 765 766 view.drop(view.getRowIndexByID(item1.id), 0, { 767 dropEffect: 'copy', 768 effectAllowed: 'copy', 769 types: { 770 contains: function (type) { 771 return type == 'zotero/item'; 772 } 773 }, 774 getData: function (type) { 775 if (type == 'zotero/item') { 776 return item3.id + ""; 777 } 778 }, 779 mozItemCount: 1 780 }) 781 782 yield promise; 783 784 // Old parent should be empty 785 assert.isFalse(view.isContainerOpen(view.getRowIndexByID(item2.id))); 786 assert.isTrue(view.isContainerEmpty(view.getRowIndexByID(item2.id))); 787 788 // New parent should be open 789 assert.isTrue(view.isContainerOpen(view.getRowIndexByID(item1.id))); 790 assert.isFalse(view.isContainerEmpty(view.getRowIndexByID(item1.id))); 791 }); 792 793 it("should create a stored top-level attachment when a file is dragged", function* () { 794 var file = getTestDataDirectory(); 795 file.append('test.png'); 796 797 var promise = itemsView.waitForSelect(); 798 799 itemsView.drop(0, -1, { 800 dropEffect: 'copy', 801 effectAllowed: 'copy', 802 types: { 803 contains: function (type) { 804 return type == 'application/x-moz-file'; 805 } 806 }, 807 mozItemCount: 1, 808 mozGetDataAt: function (type, i) { 809 if (type == 'application/x-moz-file' && i == 0) { 810 return file; 811 } 812 } 813 }) 814 815 yield promise; 816 var items = itemsView.getSelectedItems(); 817 var path = yield items[0].getFilePathAsync(); 818 assert.equal( 819 (yield Zotero.File.getBinaryContentsAsync(path)), 820 (yield Zotero.File.getBinaryContentsAsync(file)) 821 ); 822 }); 823 824 it("should create a stored top-level attachment when a URL is dragged", function* () { 825 var promise = itemsView.waitForSelect(); 826 827 itemsView.drop(0, -1, { 828 dropEffect: 'copy', 829 effectAllowed: 'copy', 830 types: { 831 contains: function (type) { 832 return type == 'text/x-moz-url'; 833 } 834 }, 835 getData: function (type) { 836 if (type == 'text/x-moz-url') { 837 return pdfURL; 838 } 839 }, 840 mozItemCount: 1, 841 }) 842 843 yield promise; 844 var item = itemsView.getSelectedItems()[0]; 845 assert.equal(item.getField('url'), pdfURL); 846 assert.equal( 847 (yield Zotero.File.getBinaryContentsAsync(yield item.getFilePathAsync())), 848 (yield Zotero.File.getBinaryContentsAsync(pdfPath)) 849 ); 850 }); 851 852 it("should create a stored child attachment when a URL is dragged", function* () { 853 var view = zp.itemsView; 854 var parentItem = yield createDataObject('item'); 855 var parentRow = view.getRowIndexByID(parentItem.id); 856 857 var promise = waitForItemEvent('add'); 858 859 itemsView.drop(parentRow, 0, { 860 dropEffect: 'copy', 861 effectAllowed: 'copy', 862 types: { 863 contains: function (type) { 864 return type == 'text/x-moz-url'; 865 } 866 }, 867 getData: function (type) { 868 if (type == 'text/x-moz-url') { 869 return pdfURL; 870 } 871 }, 872 mozItemCount: 1, 873 }) 874 875 var itemIDs = yield promise; 876 var item = Zotero.Items.get(itemIDs[0]); 877 assert.equal(item.parentItemID, parentItem.id); 878 assert.equal(item.getField('url'), pdfURL); 879 assert.equal( 880 (yield Zotero.File.getBinaryContentsAsync(yield item.getFilePathAsync())), 881 (yield Zotero.File.getBinaryContentsAsync(pdfPath)) 882 ); 883 }); 884 885 it("should automatically retrieve metadata for top-level PDF if pref is enabled", async function () { 886 Zotero.Prefs.set('autoRecognizeFiles', true); 887 888 var view = zp.itemsView; 889 890 var promise = waitForItemEvent('add'); 891 var recognizerPromise = waitForRecognizer(); 892 893 // Fake recognizer response 894 Zotero.HTTP.mock = sinon.FakeXMLHttpRequest; 895 var server = sinon.fakeServer.create(); 896 server.autoRespond = true; 897 setHTTPResponse( 898 server, 899 ZOTERO_CONFIG.RECOGNIZE_URL, 900 { 901 method: 'POST', 902 url: 'recognize', 903 status: 200, 904 headers: { 905 'Content-Type': 'application/json' 906 }, 907 json: { 908 title: 'Test', 909 authors: [] 910 } 911 } 912 ); 913 914 itemsView.drop(0, -1, { 915 dropEffect: 'copy', 916 effectAllowed: 'copy', 917 types: { 918 contains: function (type) { 919 return type == 'text/x-moz-url'; 920 } 921 }, 922 getData: function (type) { 923 if (type == 'text/x-moz-url') { 924 return pdfURL; 925 } 926 }, 927 mozItemCount: 1, 928 }) 929 930 var itemIDs = await promise; 931 var item = Zotero.Items.get(itemIDs[0]); 932 933 var progressWindow = await recognizerPromise; 934 progressWindow.close(); 935 Zotero.RecognizePDF.cancel(); 936 assert.isFalse(item.isTopLevelItem()); 937 938 Zotero.HTTP.mock = null; 939 }); 940 941 it("should rename a stored child attachment using parent metadata if no existing file attachments and pref enabled", async function () { 942 var view = zp.itemsView; 943 var parentTitle = Zotero.Utilities.randomString(); 944 var parentItem = await createDataObject('item', { title: parentTitle }); 945 await Zotero.Attachments.linkFromURL({ 946 url: 'https://example.com', 947 title: 'Example', 948 parentItemID: parentItem.id 949 }); 950 var parentRow = view.getRowIndexByID(parentItem.id); 951 952 var file = getTestDataDirectory(); 953 file.append('empty.pdf'); 954 955 var promise = waitForItemEvent('add'); 956 957 itemsView.drop(parentRow, 0, { 958 dropEffect: 'copy', 959 effectAllowed: 'copy', 960 types: { 961 contains: function (type) { 962 return type == 'application/x-moz-file'; 963 } 964 }, 965 mozItemCount: 1, 966 mozGetDataAt: function (type, i) { 967 if (type == 'application/x-moz-file' && i == 0) { 968 return file; 969 } 970 } 971 }) 972 973 var itemIDs = await promise; 974 var item = Zotero.Items.get(itemIDs[0]); 975 assert.equal(item.parentItemID, parentItem.id); 976 var title = item.getField('title'); 977 var path = await item.getFilePathAsync(); 978 assert.equal(title, parentTitle + '.pdf'); 979 assert.equal(OS.Path.basename(path), parentTitle + '.pdf'); 980 }); 981 982 it("should rename a linked child attachment using parent metadata if no existing file attachments and pref enabled", async function () { 983 var view = zp.itemsView; 984 var parentTitle = Zotero.Utilities.randomString(); 985 var parentItem = await createDataObject('item', { title: parentTitle }); 986 await Zotero.Attachments.linkFromURL({ 987 url: 'https://example.com', 988 title: 'Example', 989 parentItemID: parentItem.id 990 }); 991 var parentRow = view.getRowIndexByID(parentItem.id); 992 993 var file = OS.Path.join(await getTempDirectory(), 'empty.pdf'); 994 await OS.File.copy( 995 OS.Path.join(getTestDataDirectory().path, 'empty.pdf'), 996 file 997 ); 998 file = Zotero.File.pathToFile(file); 999 1000 var promise = waitForItemEvent('add'); 1001 1002 itemsView.drop(parentRow, 0, { 1003 dropEffect: 'link', 1004 effectAllowed: 'link', 1005 types: { 1006 contains: function (type) { 1007 return type == 'application/x-moz-file'; 1008 } 1009 }, 1010 mozItemCount: 1, 1011 mozGetDataAt: function (type, i) { 1012 if (type == 'application/x-moz-file' && i == 0) { 1013 return file; 1014 } 1015 } 1016 }) 1017 1018 var itemIDs = await promise; 1019 var item = Zotero.Items.get(itemIDs[0]); 1020 assert.equal(item.parentItemID, parentItem.id); 1021 var title = item.getField('title'); 1022 var path = await item.getFilePathAsync(); 1023 assert.equal(title, parentTitle + '.pdf'); 1024 assert.equal(OS.Path.basename(path), parentTitle + '.pdf'); 1025 }); 1026 1027 it("shouldn't rename a stored child attachment using parent metadata if pref disabled", async function () { 1028 Zotero.Prefs.set('autoRenameFiles', false); 1029 1030 var view = zp.itemsView; 1031 var parentTitle = Zotero.Utilities.randomString(); 1032 var parentItem = await createDataObject('item', { title: parentTitle }); 1033 await Zotero.Attachments.linkFromURL({ 1034 url: 'https://example.com', 1035 title: 'Example', 1036 parentItemID: parentItem.id 1037 }); 1038 var parentRow = view.getRowIndexByID(parentItem.id); 1039 1040 var originalFileName = 'empty.pdf'; 1041 var file = getTestDataDirectory(); 1042 file.append(originalFileName); 1043 1044 var promise = waitForItemEvent('add'); 1045 1046 itemsView.drop(parentRow, 0, { 1047 dropEffect: 'copy', 1048 effectAllowed: 'copy', 1049 types: { 1050 contains: function (type) { 1051 return type == 'application/x-moz-file'; 1052 } 1053 }, 1054 mozItemCount: 1, 1055 mozGetDataAt: function (type, i) { 1056 if (type == 'application/x-moz-file' && i == 0) { 1057 return file; 1058 } 1059 } 1060 }) 1061 1062 var itemIDs = await promise; 1063 var item = Zotero.Items.get(itemIDs[0]); 1064 assert.equal(item.parentItemID, parentItem.id); 1065 var title = item.getField('title'); 1066 var path = await item.getFilePathAsync(); 1067 // Should match original filename, not parent title 1068 assert.equal(title, originalFileName); 1069 assert.equal(OS.Path.basename(path), originalFileName); 1070 }); 1071 1072 it("shouldn't rename a stored child attachment using parent metadata if existing file attachments", async function () { 1073 var view = zp.itemsView; 1074 var parentTitle = Zotero.Utilities.randomString(); 1075 var parentItem = await createDataObject('item', { title: parentTitle }); 1076 await Zotero.Attachments.linkFromFile({ 1077 file: OS.Path.join(getTestDataDirectory().path, 'test.png'), 1078 parentItemID: parentItem.id 1079 }); 1080 var parentRow = view.getRowIndexByID(parentItem.id); 1081 1082 var originalFileName = 'empty.pdf'; 1083 var file = getTestDataDirectory(); 1084 file.append(originalFileName); 1085 1086 var promise = waitForItemEvent('add'); 1087 1088 itemsView.drop(parentRow, 0, { 1089 dropEffect: 'copy', 1090 effectAllowed: 'copy', 1091 types: { 1092 contains: function (type) { 1093 return type == 'application/x-moz-file'; 1094 } 1095 }, 1096 mozItemCount: 1, 1097 mozGetDataAt: function (type, i) { 1098 if (type == 'application/x-moz-file' && i == 0) { 1099 return file; 1100 } 1101 } 1102 }) 1103 1104 var itemIDs = await promise; 1105 var item = Zotero.Items.get(itemIDs[0]); 1106 assert.equal(item.parentItemID, parentItem.id); 1107 var title = item.getField('title'); 1108 var path = await item.getFilePathAsync(); 1109 assert.equal(title, originalFileName); 1110 assert.equal(OS.Path.basename(path), originalFileName); 1111 }); 1112 1113 it("shouldn't rename a stored child attachment using parent metadata if drag includes multiple files", async function () { 1114 var view = zp.itemsView; 1115 var parentTitle = Zotero.Utilities.randomString(); 1116 var parentItem = await createDataObject('item', { title: parentTitle }); 1117 var parentRow = view.getRowIndexByID(parentItem.id); 1118 1119 var originalFileName = 'empty.pdf'; 1120 var file = getTestDataDirectory(); 1121 file.append(originalFileName); 1122 1123 var promise = waitForItemEvent('add'); 1124 1125 itemsView.drop(parentRow, 0, { 1126 dropEffect: 'copy', 1127 effectAllowed: 'copy', 1128 types: { 1129 contains: function (type) { 1130 return type == 'application/x-moz-file'; 1131 } 1132 }, 1133 mozItemCount: 2, 1134 mozGetDataAt: function (type, i) { 1135 if (type == 'application/x-moz-file' && i <= 1) { 1136 return file; 1137 } 1138 } 1139 }) 1140 1141 var itemIDs = await promise; 1142 var item = Zotero.Items.get(itemIDs[0]); 1143 assert.equal(item.parentItemID, parentItem.id); 1144 var title = item.getField('title'); 1145 var path = await item.getFilePathAsync(); 1146 assert.equal(title, originalFileName); 1147 assert.equal(OS.Path.basename(path), originalFileName); 1148 }); 1149 }); 1150 })