itemTest.js (56703B)
1 "use strict"; 2 3 describe("Zotero.Item", function () { 4 describe("#getField()", function () { 5 it("should return an empty string for valid unset fields on unsaved items", function () { 6 var item = new Zotero.Item('book'); 7 assert.strictEqual(item.getField('rights'), ""); 8 }); 9 10 it("should return an empty string for valid unset fields on unsaved items after setting on another field", function () { 11 var item = new Zotero.Item('book'); 12 item.setField('title', 'foo'); 13 assert.strictEqual(item.getField('rights'), ""); 14 }); 15 16 it("should return an empty string for invalid unset fields on unsaved items after setting on another field", function () { 17 var item = new Zotero.Item('book'); 18 item.setField('title', 'foo'); 19 assert.strictEqual(item.getField('invalid'), ""); 20 }); 21 22 it("should return a firstCreator for an unsaved item", function* () { 23 var item = createUnsavedDataObject('item'); 24 item.setCreators([ 25 { 26 firstName: "A", 27 lastName: "B", 28 creatorType: "author" 29 }, 30 { 31 firstName: "C", 32 lastName: "D", 33 creatorType: "editor" 34 } 35 ]); 36 assert.equal(item.getField('firstCreator'), "B"); 37 }); 38 }); 39 40 describe("#setField", function () { 41 it("should throw an error if item type isn't set", function () { 42 var item = new Zotero.Item; 43 assert.throws(() => item.setField('title', 'test'), "Item type must be set before setting field data"); 44 }) 45 46 it("should mark a field as changed", function () { 47 var item = new Zotero.Item('book'); 48 item.setField('title', 'Foo'); 49 assert.isTrue(item._changed.itemData[Zotero.ItemFields.getID('title')]); 50 assert.isTrue(item.hasChanged()); 51 }) 52 53 it("should save an integer as a string", function* () { 54 var val = 1234; 55 var item = new Zotero.Item('book'); 56 item.setField('numPages', val); 57 yield item.saveTx(); 58 assert.strictEqual(item.getField('numPages'), "" + val); 59 // Setting again as string shouldn't register a change 60 assert.isFalse(item.setField('numPages', "" + val)); 61 62 // Value should be TEXT in the DB 63 var sql = "SELECT TYPEOF(value) FROM itemData JOIN itemDataValues USING (valueID) " 64 + "WHERE itemID=? AND fieldID=?"; 65 var type = yield Zotero.DB.valueQueryAsync(sql, [item.id, Zotero.ItemFields.getID('numPages')]); 66 assert.equal(type, 'text'); 67 }); 68 69 it("should save integer 0 as a string", function* () { 70 var val = 0; 71 var item = new Zotero.Item('book'); 72 item.setField('numPages', val); 73 yield item.saveTx(); 74 assert.strictEqual(item.getField('numPages'), "" + val); 75 // Setting again as string shouldn't register a change 76 assert.isFalse(item.setField('numPages', "" + val)); 77 }); 78 79 it('should clear an existing field when ""/null/false is passed', function* () { 80 var field = 'title'; 81 var val = 'foo'; 82 var fieldID = Zotero.ItemFields.getID(field); 83 var item = new Zotero.Item('book'); 84 item.setField(field, val); 85 yield item.saveTx(); 86 87 item.setField(field, ""); 88 assert.ok(item._changed.itemData[fieldID]); 89 assert.isTrue(item.hasChanged()); 90 91 // Reset to original value 92 yield item.reload(); 93 assert.isFalse(item.hasChanged()); 94 assert.equal(item.getField(field), val); 95 96 // false 97 item.setField(field, false); 98 assert.ok(item._changed.itemData[fieldID]); 99 assert.isTrue(item.hasChanged()); 100 101 // Reset to original value 102 yield item.reload(); 103 assert.isFalse(item.hasChanged()); 104 assert.equal(item.getField(field), val); 105 106 // null 107 item.setField(field, null); 108 assert.ok(item._changed.itemData[fieldID]); 109 assert.isTrue(item.hasChanged()); 110 111 yield item.saveTx(); 112 assert.equal(item.getField(field), ""); 113 }) 114 115 it('should clear a field set to "0" when a ""/null/false is passed', function* () { 116 var field = 'title'; 117 var val = "0"; 118 var fieldID = Zotero.ItemFields.getID(field); 119 var item = new Zotero.Item('book'); 120 item.setField(field, val); 121 yield item.saveTx(); 122 123 assert.strictEqual(item.getField(field), val); 124 125 // "" 126 item.setField(field, ""); 127 assert.ok(item._changed.itemData[fieldID]); 128 assert.isTrue(item.hasChanged()); 129 130 // Reset to original value 131 yield item.reload(); 132 assert.isFalse(item.hasChanged()); 133 assert.strictEqual(item.getField(field), val); 134 135 // False 136 item.setField(field, false); 137 assert.ok(item._changed.itemData[fieldID]); 138 assert.isTrue(item.hasChanged()); 139 140 // Reset to original value 141 yield item.reload(); 142 assert.isFalse(item.hasChanged()); 143 assert.strictEqual(item.getField(field), val); 144 145 // null 146 item.setField(field, null); 147 assert.ok(item._changed.itemData[fieldID]); 148 assert.isTrue(item.hasChanged()); 149 150 yield item.saveTx(); 151 assert.strictEqual(item.getField(field), ""); 152 }) 153 154 it("should throw if value is undefined", function () { 155 var item = new Zotero.Item('book'); 156 assert.throws(() => item.setField('title'), "'title' value cannot be undefined"); 157 }) 158 159 it("should not mark an empty field set to an empty string as changed", function () { 160 var item = new Zotero.Item('book'); 161 item.setField('url', ''); 162 assert.isUndefined(item._changed.itemData); 163 }) 164 165 it("should save version as object version", function* () { 166 var item = new Zotero.Item('book'); 167 item.setField("version", 1); 168 var id = yield item.saveTx(); 169 item = yield Zotero.Items.getAsync(id); 170 assert.equal(item.getField("version"), 1); 171 assert.equal(item.version, 1); 172 }); 173 174 it("should save versionNumber for computerProgram", function* () { 175 var item = new Zotero.Item('computerProgram'); 176 item.setField("versionNumber", "1.0"); 177 var id = yield item.saveTx(); 178 item = yield Zotero.Items.getAsync(id); 179 assert.equal(item.getField("versionNumber"), "1.0"); 180 }); 181 182 it("should accept ISO 8601 dates", function* () { 183 var fields = { 184 accessDate: "2015-06-07T20:56:00Z", 185 dateAdded: "2015-06-07T20:57:00Z", 186 dateModified: "2015-06-07T20:58:00Z", 187 }; 188 var item = createUnsavedDataObject('item'); 189 for (let i in fields) { 190 item.setField(i, fields[i]); 191 } 192 assert.equal(item.getField('accessDate'), '2015-06-07 20:56:00'); 193 assert.equal(item.dateAdded, '2015-06-07 20:57:00'); 194 assert.equal(item.dateModified, '2015-06-07 20:58:00'); 195 }) 196 197 it("should accept SQL dates", function* () { 198 var fields = { 199 accessDate: "2015-06-07 20:56:00", 200 dateAdded: "2015-06-07 20:57:00", 201 dateModified: "2015-06-07 20:58:00", 202 }; 203 var item = createUnsavedDataObject('item'); 204 for (let i in fields) { 205 item.setField(i, fields[i]); 206 item.getField(i, fields[i]); 207 } 208 }) 209 210 it("should accept SQL accessDate without time", function* () { 211 var item = createUnsavedDataObject('item'); 212 var date = "2017-04-05"; 213 item.setField("accessDate", date); 214 assert.strictEqual(item.getField('accessDate'), date); 215 }); 216 217 it("should ignore unknown accessDate values", function* () { 218 var fields = { 219 accessDate: "foo" 220 }; 221 var item = createUnsavedDataObject('item'); 222 for (let i in fields) { 223 item.setField(i, fields[i]); 224 } 225 assert.strictEqual(item.getField('accessDate'), ''); 226 }) 227 }) 228 229 describe("#dateAdded", function () { 230 it("should use current time if value was not given for a new item", function* () { 231 var item = new Zotero.Item('book'); 232 var id = yield item.saveTx(); 233 item = Zotero.Items.get(id); 234 235 assert.closeTo(Zotero.Date.sqlToDate(item.dateAdded, true).getTime(), Date.now(), 2000); 236 }) 237 238 it("should use given value for a new item", function* () { 239 var dateAdded = "2015-05-05 17:18:12"; 240 var item = new Zotero.Item('book'); 241 item.dateAdded = dateAdded; 242 var id = yield item.saveTx(); 243 item = yield Zotero.Items.getAsync(id); 244 assert.equal(item.dateAdded, dateAdded); 245 }) 246 }) 247 248 describe("#dateModified", function () { 249 it("should use given value for a new item", function* () { 250 var dateModified = "2015-05-05 17:18:12"; 251 var item = new Zotero.Item('book'); 252 item.dateModified = dateModified; 253 var id = yield item.saveTx(); 254 assert.equal(item.dateModified, dateModified); 255 item = yield Zotero.Items.getAsync(id); 256 assert.equal(item.dateModified, dateModified); 257 }) 258 259 it("should use given value when skipDateModifiedUpdate is set for a new item", function* () { 260 var dateModified = "2015-05-05 17:18:12"; 261 var item = new Zotero.Item('book'); 262 item.dateModified = dateModified; 263 var id = yield item.saveTx({ 264 skipDateModifiedUpdate: true 265 }); 266 assert.equal(item.dateModified, dateModified); 267 item = yield Zotero.Items.getAsync(id); 268 assert.equal(item.dateModified, dateModified); 269 }) 270 271 it("should use current time if value was not given for an existing item", function* () { 272 var dateModified = "2015-05-05 17:18:12"; 273 var item = new Zotero.Item('book'); 274 item.dateModified = dateModified; 275 var id = yield item.saveTx(); 276 item = Zotero.Items.get(id); 277 278 // Save again without changing Date Modified 279 item.setField('title', 'Test'); 280 yield item.saveTx() 281 282 assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 2000); 283 }) 284 285 it("should use current time if the existing value was given for an existing item", function* () { 286 var dateModified = "2015-05-05 17:18:12"; 287 var item = new Zotero.Item('book'); 288 item.dateModified = dateModified; 289 var id = yield item.saveTx(); 290 item = Zotero.Items.get(id); 291 292 // Set Date Modified to existing value 293 item.setField('title', 'Test'); 294 item.dateModified = dateModified; 295 yield item.saveTx() 296 assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 2000); 297 }) 298 299 it("should use current time if value is not given when skipDateModifiedUpdate is set for a new item", function* () { 300 var item = new Zotero.Item('book'); 301 var id = yield item.saveTx({ 302 skipDateModifiedUpdate: true 303 }); 304 item = yield Zotero.Items.getAsync(id); 305 assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 2000); 306 }) 307 308 it("should keep original value when skipDateModifiedUpdate is set for an existing item", function* () { 309 var dateModified = "2015-05-05 17:18:12"; 310 var item = new Zotero.Item('book'); 311 item.dateModified = dateModified; 312 var id = yield item.saveTx(); 313 item = Zotero.Items.get(id); 314 315 // Resave with skipDateModifiedUpdate 316 item.setField('title', 'Test'); 317 yield item.saveTx({ 318 skipDateModifiedUpdate: true 319 }) 320 assert.equal(item.dateModified, dateModified); 321 }) 322 }) 323 324 describe("#deleted", function () { 325 it("should be set to true after save", function* () { 326 var item = yield createDataObject('item'); 327 item.deleted = true; 328 // Sanity check for itemsTest#trash() 329 assert.isTrue(item._changed.deleted); 330 yield item.saveTx(); 331 assert.ok(item.deleted); 332 }) 333 334 it("should be set to false after save", function* () { 335 var collection = yield createDataObject('collection'); 336 var item = createUnsavedDataObject('item'); 337 item.deleted = true; 338 yield item.saveTx(); 339 340 item.deleted = false; 341 yield item.saveTx(); 342 assert.isFalse(item.deleted); 343 }) 344 }) 345 346 describe("#inPublications", function () { 347 it("should add item to publications table", function* () { 348 var item = yield createDataObject('item'); 349 item.inPublications = true; 350 yield item.saveTx(); 351 assert.ok(item.inPublications); 352 assert.equal( 353 (yield Zotero.DB.valueQueryAsync( 354 "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)), 355 1 356 ); 357 }) 358 359 it("should be set to false after save", function* () { 360 var collection = yield createDataObject('collection'); 361 var item = createUnsavedDataObject('item'); 362 item.inPublications = false; 363 yield item.saveTx(); 364 365 item.inPublications = false; 366 yield item.saveTx(); 367 assert.isFalse(item.inPublications); 368 assert.equal( 369 (yield Zotero.DB.valueQueryAsync( 370 "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)), 371 0 372 ); 373 }); 374 375 it("should be invalid for linked-file attachments", function* () { 376 var item = yield createDataObject('item', { inPublications: true }); 377 var attachment = yield Zotero.Attachments.linkFromFile({ 378 file: OS.Path.join(getTestDataDirectory().path, 'test.png'), 379 parentItemID: item.id 380 }); 381 attachment.inPublications = true; 382 var e = yield getPromiseError(attachment.saveTx()); 383 assert.ok(e); 384 assert.include(e.message, "Linked-file attachments cannot be added to My Publications"); 385 }); 386 387 it("should be invalid for group library items", function* () { 388 var group = yield getGroup(); 389 var item = yield createDataObject('item', { libraryID: group.libraryID }); 390 item.inPublications = true; 391 var e = yield getPromiseError(item.saveTx()); 392 assert.ok(e); 393 assert.equal(e.message, "Only items in user libraries can be added to My Publications"); 394 }); 395 }); 396 397 describe("#parentID", function () { 398 it("should create a child note", function* () { 399 var item = new Zotero.Item('book'); 400 var parentItemID = yield item.saveTx(); 401 402 item = new Zotero.Item('note'); 403 item.parentID = parentItemID; 404 var childItemID = yield item.saveTx(); 405 406 item = yield Zotero.Items.getAsync(childItemID); 407 assert.ok(item.parentID); 408 assert.equal(item.parentID, parentItemID); 409 }); 410 }); 411 412 describe("#parentKey", function () { 413 it("should be false for an unsaved attachment", function () { 414 var item = new Zotero.Item('attachment'); 415 assert.isFalse(item.parentKey); 416 }); 417 418 it("should be false on an unsaved non-attachment item", function () { 419 var item = new Zotero.Item('book'); 420 assert.isFalse(item.parentKey); 421 }); 422 423 it("should not be marked as changed setting to false on an unsaved item", function () { 424 var item = new Zotero.Item('attachment'); 425 item.attachmentLinkMode = 'linked_url'; 426 item.parentKey = false; 427 assert.isUndefined(item._changed.parentKey); 428 }); 429 430 it("should not mark item as changed if false and no existing parent", function* () { 431 var item = new Zotero.Item('attachment'); 432 item.attachmentLinkMode = 'linked_url'; 433 item.url = "https://www.zotero.org/"; 434 var id = yield item.saveTx(); 435 item = yield Zotero.Items.getAsync(id); 436 437 item.parentKey = false; 438 assert.isFalse(item.hasChanged()); 439 }); 440 441 it("should not be marked as changed after a save", async function () { 442 var item = await createDataObject('item'); 443 var attachment = new Zotero.Item('attachment'); 444 attachment.attachmentLinkMode = 'linked_url'; 445 await attachment.saveTx(); 446 447 attachment.parentKey = item.key; 448 assert.isTrue(attachment._changed.parentKey); 449 await attachment.saveTx(); 450 assert.isUndefined(attachment._changed.parentKey); 451 }); 452 453 it("should move a top-level note under another item", function* () { 454 var noteItem = new Zotero.Item('note'); 455 var id = yield noteItem.saveTx() 456 noteItem = yield Zotero.Items.getAsync(id); 457 458 var item = new Zotero.Item('book'); 459 id = yield item.saveTx(); 460 var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id); 461 462 noteItem.parentKey = key; 463 yield noteItem.saveTx(); 464 465 assert.isFalse(noteItem.isTopLevelItem()); 466 }) 467 468 it("should remove top-level item from collections when moving it under another item", function* () { 469 // Create a collection 470 var collection = new Zotero.Collection; 471 collection.name = "Test"; 472 var collectionID = yield collection.saveTx(); 473 474 // Create a top-level note and add it to a collection 475 var noteItem = new Zotero.Item('note'); 476 noteItem.addToCollection(collectionID); 477 var id = yield noteItem.saveTx() 478 noteItem = yield Zotero.Items.getAsync(id); 479 480 var item = new Zotero.Item('book'); 481 id = yield item.saveTx(); 482 var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id); 483 noteItem.parentKey = key; 484 yield noteItem.saveTx(); 485 486 assert.isFalse(noteItem.isTopLevelItem()); 487 }) 488 }); 489 490 describe("#getCreators()", function () { 491 it("should update after creators are removed", function* () { 492 var item = createUnsavedDataObject('item'); 493 item.setCreators([ 494 { 495 creatorType: "author", 496 name: "A" 497 } 498 ]); 499 yield item.saveTx(); 500 501 assert.lengthOf(item.getCreators(), 1); 502 503 item.setCreators([]); 504 yield item.saveTx(); 505 506 assert.lengthOf(item.getCreators(), 0); 507 }); 508 }); 509 510 describe("#setCreators", function () { 511 it("should accept an array of creators in API JSON format", function* () { 512 var creators = [ 513 { 514 firstName: "First", 515 lastName: "Last", 516 creatorType: "author" 517 }, 518 { 519 name: "Test Name", 520 creatorType: "editor" 521 } 522 ]; 523 524 var item = new Zotero.Item("journalArticle"); 525 item.setCreators(creators); 526 var id = yield item.saveTx(); 527 item = Zotero.Items.get(id); 528 assert.sameDeepMembers(item.getCreatorsJSON(), creators); 529 }) 530 531 it("should accept an array of creators in internal format", function* () { 532 var creators = [ 533 { 534 firstName: "First", 535 lastName: "Last", 536 fieldMode: 0, 537 creatorTypeID: 1 538 }, 539 { 540 firstName: "", 541 lastName: "Test Name", 542 fieldMode: 1, 543 creatorTypeID: 2 544 } 545 ]; 546 547 var item = new Zotero.Item("journalArticle"); 548 item.setCreators(creators); 549 var id = yield item.saveTx(); 550 item = Zotero.Items.get(id); 551 assert.sameDeepMembers(item.getCreators(), creators); 552 }) 553 554 it("should clear creators if empty array passed", function () { 555 var item = createUnsavedDataObject('item'); 556 item.setCreators([ 557 { 558 firstName: "First", 559 lastName: "Last", 560 fieldMode: 0, 561 creatorTypeID: 1 562 } 563 ]); 564 assert.lengthOf(item.getCreators(), 1); 565 item.setCreators([]); 566 assert.lengthOf(item.getCreators(), 0); 567 }); 568 }) 569 570 571 describe("#numAttachments()", function () { 572 it("should include child attachments", function* () { 573 var item = yield createDataObject('item'); 574 var attachment = yield importFileAttachment('test.png', { parentID: item.id }); 575 assert.equal(item.numAttachments(), 1); 576 }); 577 578 it("shouldn't include trashed child attachments by default", function* () { 579 var item = yield createDataObject('item'); 580 yield importFileAttachment('test.png', { parentID: item.id }); 581 var attachment = yield importFileAttachment('test.png', { parentID: item.id }); 582 attachment.deleted = true; 583 yield attachment.saveTx(); 584 assert.equal(item.numAttachments(), 1); 585 }); 586 587 it("should include trashed child attachments if includeTrashed=true", function* () { 588 var item = yield createDataObject('item'); 589 yield importFileAttachment('test.png', { parentID: item.id }); 590 var attachment = yield importFileAttachment('test.png', { parentID: item.id }); 591 attachment.deleted = true; 592 yield attachment.saveTx(); 593 assert.equal(item.numAttachments(true), 2); 594 }); 595 }); 596 597 598 describe("#getAttachments()", function () { 599 it("#should return child attachments", function* () { 600 var item = yield createDataObject('item'); 601 var attachment = new Zotero.Item("attachment"); 602 attachment.parentID = item.id; 603 attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; 604 yield attachment.saveTx(); 605 606 var attachments = item.getAttachments(); 607 assert.lengthOf(attachments, 1); 608 assert.equal(attachments[0], attachment.id); 609 }) 610 611 it("#should ignore trashed child attachments by default", function* () { 612 var item = yield createDataObject('item'); 613 var attachment = new Zotero.Item("attachment"); 614 attachment.parentID = item.id; 615 attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; 616 attachment.deleted = true; 617 yield attachment.saveTx(); 618 619 var attachments = item.getAttachments(); 620 assert.lengthOf(attachments, 0); 621 }) 622 623 it("#should include trashed child attachments if includeTrashed=true", function* () { 624 var item = yield createDataObject('item'); 625 var attachment = new Zotero.Item("attachment"); 626 attachment.parentID = item.id; 627 attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; 628 attachment.deleted = true; 629 yield attachment.saveTx(); 630 631 var attachments = item.getAttachments(true); 632 assert.lengthOf(attachments, 1); 633 assert.equal(attachments[0], attachment.id); 634 }) 635 636 it("#should return an empty array for an item with no attachments", function* () { 637 var item = yield createDataObject('item'); 638 assert.lengthOf(item.getAttachments(), 0); 639 }) 640 641 it("should update after an attachment is moved to another item", function* () { 642 var item1 = yield createDataObject('item'); 643 var item2 = yield createDataObject('item'); 644 var item3 = new Zotero.Item('attachment'); 645 item3.parentID = item1.id; 646 item3.attachmentLinkMode = 'linked_url'; 647 item3.setField('url', 'http://example.com'); 648 yield item3.saveTx(); 649 650 assert.lengthOf(item1.getAttachments(), 1); 651 assert.lengthOf(item2.getAttachments(), 0); 652 653 item3.parentID = item2.id; 654 yield item3.saveTx(); 655 656 assert.lengthOf(item1.getAttachments(), 0); 657 assert.lengthOf(item2.getAttachments(), 1); 658 }); 659 }) 660 661 describe("#numNotes()", function () { 662 it("should include child notes", function* () { 663 var item = yield createDataObject('item'); 664 yield createDataObject('item', { itemType: 'note', parentID: item.id }); 665 yield createDataObject('item', { itemType: 'note', parentID: item.id }); 666 assert.equal(item.numNotes(), 2); 667 }); 668 669 it("shouldn't include trashed child notes by default", function* () { 670 var item = yield createDataObject('item'); 671 yield createDataObject('item', { itemType: 'note', parentID: item.id }); 672 yield createDataObject('item', { itemType: 'note', parentID: item.id, deleted: true }); 673 assert.equal(item.numNotes(), 1); 674 }); 675 676 it("should include trashed child notes with includeTrashed", function* () { 677 var item = yield createDataObject('item'); 678 yield createDataObject('item', { itemType: 'note', parentID: item.id }); 679 yield createDataObject('item', { itemType: 'note', parentID: item.id, deleted: true }); 680 assert.equal(item.numNotes(true), 2); 681 }); 682 683 it("should include child attachment notes with includeEmbedded", function* () { 684 var item = yield createDataObject('item'); 685 yield createDataObject('item', { itemType: 'note', parentID: item.id }); 686 var attachment = yield importFileAttachment('test.png', { parentID: item.id }); 687 attachment.setNote('test'); 688 yield attachment.saveTx(); 689 yield item.loadDataType('childItems'); 690 assert.equal(item.numNotes(false, true), 2); 691 }); 692 693 it("shouldn't include empty child attachment notes with includeEmbedded", function* () { 694 var item = yield createDataObject('item'); 695 yield createDataObject('item', { itemType: 'note', parentID: item.id }); 696 var attachment = yield importFileAttachment('test.png', { parentID: item.id }); 697 assert.equal(item.numNotes(false, true), 1); 698 }); 699 700 // TODO: Fix numNotes(false, true) updating after child attachment note is added or removed 701 }); 702 703 704 describe("#getNotes()", function () { 705 it("#should return child notes", function* () { 706 var item = yield createDataObject('item'); 707 var note = new Zotero.Item("note"); 708 note.parentID = item.id; 709 yield note.saveTx(); 710 711 var notes = item.getNotes(); 712 assert.lengthOf(notes, 1); 713 assert.equal(notes[0], note.id); 714 }) 715 716 it("#should ignore trashed child notes by default", function* () { 717 var item = yield createDataObject('item'); 718 var note = new Zotero.Item("note"); 719 note.parentID = item.id; 720 note.deleted = true; 721 yield note.saveTx(); 722 723 var notes = item.getNotes(); 724 assert.lengthOf(notes, 0); 725 }) 726 727 it("#should include trashed child notes if includeTrashed=true", function* () { 728 var item = yield createDataObject('item'); 729 var note = new Zotero.Item("note"); 730 note.parentID = item.id; 731 note.deleted = true; 732 yield note.saveTx(); 733 734 var notes = item.getNotes(true); 735 assert.lengthOf(notes, 1); 736 assert.equal(notes[0], note.id); 737 }) 738 739 it("#should return an empty array for an item with no notes", function* () { 740 var item = yield createDataObject('item'); 741 assert.lengthOf(item.getNotes(), 0); 742 }); 743 744 it("should update after a note is moved to another item", function* () { 745 var item1 = yield createDataObject('item'); 746 var item2 = yield createDataObject('item'); 747 var item3 = yield createDataObject('item', { itemType: 'note', parentID: item1.id }); 748 749 assert.lengthOf(item1.getNotes(), 1); 750 assert.lengthOf(item2.getNotes(), 0); 751 752 item3.parentID = item2.id; 753 yield item3.saveTx(); 754 755 assert.lengthOf(item1.getNotes(), 0); 756 assert.lengthOf(item2.getNotes(), 1); 757 }); 758 }) 759 760 describe("#attachmentCharset", function () { 761 it("should get and set a value", function* () { 762 var charset = 'utf-8'; 763 var item = new Zotero.Item("attachment"); 764 item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; 765 item.attachmentCharset = charset; 766 var itemID = yield item.saveTx(); 767 assert.equal(item.attachmentCharset, charset); 768 item = yield Zotero.Items.getAsync(itemID); 769 assert.equal(item.attachmentCharset, charset); 770 }) 771 772 it("should not allow a numerical value", function* () { 773 var charset = 1; 774 var item = new Zotero.Item("attachment"); 775 try { 776 item.attachmentCharset = charset; 777 } 778 catch (e) { 779 assert.equal(e.message, "Character set must be a string") 780 return; 781 } 782 assert.fail("Numerical charset was allowed"); 783 }) 784 785 it("should not be marked as changed if not changed", function* () { 786 var charset = 'utf-8'; 787 var item = new Zotero.Item("attachment"); 788 item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; 789 item.attachmentCharset = charset; 790 var itemID = yield item.saveTx(); 791 item = yield Zotero.Items.getAsync(itemID); 792 793 // Set charset to same value 794 item.attachmentCharset = charset 795 assert.isFalse(item.hasChanged()); 796 }) 797 }) 798 799 describe("#attachmentFilename", function () { 800 it("should get and set a filename for a stored file", function* () { 801 var filename = "test.txt"; 802 803 // Create parent item 804 var item = new Zotero.Item("book"); 805 var parentItemID = yield item.saveTx(); 806 807 // Create attachment item 808 var item = new Zotero.Item("attachment"); 809 item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; 810 item.parentID = parentItemID; 811 var itemID = yield item.saveTx(); 812 813 // Should be empty when unset 814 assert.equal(item.attachmentFilename, ''); 815 816 // Set filename 817 item.attachmentFilename = filename; 818 yield item.saveTx(); 819 item = yield Zotero.Items.getAsync(itemID); 820 821 // Check filename 822 assert.equal(item.attachmentFilename, filename); 823 824 // Check full path 825 var file = Zotero.Attachments.getStorageDirectory(item); 826 file.append(filename); 827 assert.equal(item.getFilePath(), file.path); 828 }); 829 830 it.skip("should get and set a filename for a base-dir-relative file", function* () { 831 832 }) 833 }) 834 835 describe("#attachmentPath", function () { 836 it("should return an absolute path for a linked attachment", function* () { 837 var file = getTestDataDirectory(); 838 file.append('test.png'); 839 var item = yield Zotero.Attachments.linkFromFile({ file }); 840 assert.equal(item.attachmentPath, file.path); 841 }) 842 843 it("should return a prefixed path for an imported file", function* () { 844 var file = getTestDataDirectory(); 845 file.append('test.png'); 846 var item = yield Zotero.Attachments.importFromFile({ file }); 847 848 assert.equal(item.attachmentPath, "storage:test.png"); 849 }) 850 851 it("should set a prefixed relative path for a path within the defined base directory", function* () { 852 var dir = getTestDataDirectory().path; 853 var dirname = OS.Path.basename(dir); 854 var baseDir = OS.Path.dirname(dir); 855 Zotero.Prefs.set('saveRelativeAttachmentPath', true) 856 Zotero.Prefs.set('baseAttachmentPath', baseDir) 857 858 var file = OS.Path.join(dir, 'test.png'); 859 860 var item = new Zotero.Item('attachment'); 861 item.attachmentLinkMode = 'linked_file'; 862 item.attachmentPath = file; 863 864 assert.equal(item.attachmentPath, "attachments:data/test.png"); 865 866 Zotero.Prefs.set('saveRelativeAttachmentPath', false) 867 Zotero.Prefs.clear('baseAttachmentPath') 868 }) 869 870 it("should return a prefixed path for a linked attachment within the defined base directory", function* () { 871 var dir = getTestDataDirectory().path; 872 var dirname = OS.Path.basename(dir); 873 var baseDir = OS.Path.dirname(dir); 874 Zotero.Prefs.set('saveRelativeAttachmentPath', true) 875 Zotero.Prefs.set('baseAttachmentPath', baseDir) 876 877 var file = OS.Path.join(dir, 'test.png'); 878 879 var item = yield Zotero.Attachments.linkFromFile({ 880 file: Zotero.File.pathToFile(file) 881 }); 882 883 assert.equal(item.attachmentPath, "attachments:data/test.png"); 884 885 Zotero.Prefs.set('saveRelativeAttachmentPath', false) 886 Zotero.Prefs.clear('baseAttachmentPath') 887 }) 888 }) 889 890 describe("#renameAttachmentFile()", function () { 891 it("should rename an attached file", function* () { 892 var file = getTestDataDirectory(); 893 file.append('test.png'); 894 var item = yield Zotero.Attachments.importFromFile({ 895 file: file 896 }); 897 var newName = 'test2.png'; 898 yield item.renameAttachmentFile(newName); 899 assert.equal(item.attachmentFilename, newName); 900 var path = yield item.getFilePathAsync(); 901 assert.equal(OS.Path.basename(path), newName) 902 yield OS.File.exists(path); 903 904 // File should be flagged for upload 905 // DEBUG: Is this necessary? 906 assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD); 907 assert.isNull(item.attachmentSyncedHash); 908 }) 909 910 it("should rename a linked file", function* () { 911 var filename = 'test.png'; 912 var file = getTestDataDirectory(); 913 file.append(filename); 914 var tmpDir = yield getTempDirectory(); 915 var tmpFile = OS.Path.join(tmpDir, filename); 916 yield OS.File.copy(file.path, tmpFile); 917 918 var item = yield Zotero.Attachments.linkFromFile({ 919 file: tmpFile 920 }); 921 var newName = 'test2.png'; 922 yield assert.eventually.isTrue(item.renameAttachmentFile(newName)); 923 assert.equal(item.attachmentFilename, newName); 924 var path = yield item.getFilePathAsync(); 925 assert.equal(OS.Path.basename(path), newName) 926 yield OS.File.exists(path); 927 }) 928 }) 929 930 931 describe("#getBestAttachmentState()", function () { 932 it("should cache state for an existing file", function* () { 933 var parentItem = yield createDataObject('item'); 934 var file = getTestDataDirectory(); 935 file.append('test.png'); 936 var childItem = yield Zotero.Attachments.importFromFile({ 937 file, 938 parentItemID: parentItem.id 939 }); 940 yield parentItem.getBestAttachmentState(); 941 assert.equal(parentItem.getBestAttachmentStateCached(), 1); 942 }) 943 944 it("should cache state for a missing file", function* () { 945 var parentItem = yield createDataObject('item'); 946 var file = getTestDataDirectory(); 947 file.append('test.png'); 948 var childItem = yield Zotero.Attachments.importFromFile({ 949 file, 950 parentItemID: parentItem.id 951 }); 952 let path = yield childItem.getFilePathAsync(); 953 yield OS.File.remove(path); 954 yield parentItem.getBestAttachmentState(); 955 assert.equal(parentItem.getBestAttachmentStateCached(), -1); 956 }) 957 }) 958 959 960 describe("#fileExists()", function () { 961 it("should cache state for an existing file", function* () { 962 var file = getTestDataDirectory(); 963 file.append('test.png'); 964 var item = yield Zotero.Attachments.importFromFile({ file }); 965 yield item.fileExists(); 966 assert.equal(item.fileExistsCached(), true); 967 }) 968 969 it("should cache state for a missing file", function* () { 970 var file = getTestDataDirectory(); 971 file.append('test.png'); 972 var item = yield Zotero.Attachments.importFromFile({ file }); 973 let path = yield item.getFilePathAsync(); 974 yield OS.File.remove(path); 975 yield item.fileExists(); 976 assert.equal(item.fileExistsCached(), false); 977 }) 978 }) 979 980 981 describe("#relinkAttachmentFile", function () { 982 it("should copy a file elsewhere into the storage directory", function* () { 983 var filename = 'test.png'; 984 var file = getTestDataDirectory(); 985 file.append(filename); 986 var tmpDir = yield getTempDirectory(); 987 var tmpFile = OS.Path.join(tmpDir, filename); 988 yield OS.File.copy(file.path, tmpFile); 989 file = OS.Path.join(tmpDir, filename); 990 991 var item = yield Zotero.Attachments.importFromFile({ file }); 992 let path = yield item.getFilePathAsync(); 993 yield OS.File.remove(path); 994 yield OS.File.removeEmptyDir(OS.Path.dirname(path)); 995 996 assert.isFalse(yield item.fileExists()); 997 yield item.relinkAttachmentFile(file); 998 assert.isTrue(yield item.fileExists()); 999 1000 assert.isTrue(yield OS.File.exists(tmpFile)); 1001 }); 1002 1003 it("should handle normalized filenames", function* () { 1004 var item = yield importFileAttachment('test.png'); 1005 var path = yield item.getFilePathAsync(); 1006 var dir = OS.Path.dirname(path); 1007 var filename = 'tést.pdf'.normalize('NFKD'); 1008 1009 // Make sure we're actually testing something -- the test string should be differently 1010 // normalized from what's done in getValidFileName 1011 assert.notEqual(filename, Zotero.File.getValidFileName(filename)); 1012 1013 var newPath = OS.Path.join(dir, filename); 1014 yield OS.File.move(path, newPath); 1015 1016 assert.isFalse(yield item.fileExists()); 1017 yield item.relinkAttachmentFile(newPath); 1018 assert.isTrue(yield item.fileExists()); 1019 }); 1020 }); 1021 1022 1023 describe("#setTags", function () { 1024 it("should save an array of tags in API JSON format", function* () { 1025 var tags = [ 1026 { 1027 tag: "A" 1028 }, 1029 { 1030 tag: "B" 1031 } 1032 ]; 1033 var item = new Zotero.Item('journalArticle'); 1034 item.setTags(tags); 1035 var id = yield item.saveTx(); 1036 item = Zotero.Items.get(id); 1037 assert.sameDeepMembers(item.getTags(tags), tags); 1038 }) 1039 1040 it("shouldn't mark item as changed if tags haven't changed", function* () { 1041 var tags = [ 1042 { 1043 tag: "A" 1044 }, 1045 { 1046 tag: "B" 1047 } 1048 ]; 1049 var item = new Zotero.Item('journalArticle'); 1050 item.setTags(tags); 1051 var id = yield item.saveTx(); 1052 item = Zotero.Items.get(id); 1053 item.setTags(tags); 1054 assert.isFalse(item.hasChanged()); 1055 }) 1056 1057 it("should remove an existing tag", function* () { 1058 var tags = [ 1059 { 1060 tag: "A" 1061 }, 1062 { 1063 tag: "B" 1064 } 1065 ]; 1066 var item = new Zotero.Item('journalArticle'); 1067 item.setTags(tags); 1068 var id = yield item.saveTx(); 1069 item = Zotero.Items.get(id); 1070 item.setTags(tags.slice(0)); 1071 yield item.saveTx(); 1072 assert.sameDeepMembers(item.getTags(tags), tags.slice(0)); 1073 }) 1074 }) 1075 1076 describe("#addTag", function () { 1077 it("should add a tag", function* () { 1078 var item = createUnsavedDataObject('item'); 1079 item.addTag('a'); 1080 yield item.saveTx(); 1081 var tags = item.getTags(); 1082 assert.deepEqual(tags, [{ tag: 'a' }]); 1083 }) 1084 1085 it("should add two tags", function* () { 1086 var item = createUnsavedDataObject('item'); 1087 item.addTag('a'); 1088 item.addTag('b'); 1089 yield item.saveTx(); 1090 var tags = item.getTags(); 1091 assert.sameDeepMembers(tags, [{ tag: 'a' }, { tag: 'b' }]); 1092 }) 1093 1094 it("should add two tags of different types", function* () { 1095 var item = createUnsavedDataObject('item'); 1096 item.addTag('a'); 1097 item.addTag('b', 1); 1098 yield item.saveTx(); 1099 var tags = item.getTags(); 1100 assert.sameDeepMembers(tags, [{ tag: 'a' }, { tag: 'b', type: 1 }]); 1101 }) 1102 1103 it("should add a tag to an existing item", function* () { 1104 var item = yield createDataObject('item'); 1105 item.addTag('a'); 1106 yield item.saveTx(); 1107 var tags = item.getTags(); 1108 assert.deepEqual(tags, [{ tag: 'a' }]); 1109 }) 1110 1111 it("should add two tags to an existing item", function* () { 1112 var item = yield createDataObject('item'); 1113 item.addTag('a'); 1114 item.addTag('b'); 1115 yield item.saveTx(); 1116 var tags = item.getTags(); 1117 assert.sameDeepMembers(tags, [{ tag: 'a' }, { tag: 'b' }]); 1118 }) 1119 }) 1120 1121 // 1122 // Relations and related items 1123 // 1124 describe("#addRelatedItem", function () { 1125 it("should add a dc:relation relation to an item", function* () { 1126 var item1 = yield createDataObject('item'); 1127 var item2 = yield createDataObject('item'); 1128 item1.addRelatedItem(item2); 1129 yield item1.saveTx(); 1130 1131 var rels = item1.getRelationsByPredicate(Zotero.Relations.relatedItemPredicate); 1132 assert.lengthOf(rels, 1); 1133 assert.equal(rels[0], Zotero.URI.getItemURI(item2)); 1134 }) 1135 1136 it("should allow an unsaved item to be related to an item in the user library", function* () { 1137 var item1 = yield createDataObject('item'); 1138 var item2 = createUnsavedDataObject('item'); 1139 item2.addRelatedItem(item1); 1140 yield item2.saveTx(); 1141 1142 var rels = item2.getRelationsByPredicate(Zotero.Relations.relatedItemPredicate); 1143 assert.lengthOf(rels, 1); 1144 assert.equal(rels[0], Zotero.URI.getItemURI(item1)); 1145 }) 1146 1147 it("should throw an error for a relation in a different library", function* () { 1148 var group = yield getGroup(); 1149 var item1 = yield createDataObject('item'); 1150 var item2 = yield createDataObject('item', { libraryID: group.libraryID }); 1151 try { 1152 item1.addRelatedItem(item2) 1153 } 1154 catch (e) { 1155 assert.ok(e); 1156 assert.equal(e.message, "Cannot relate item to an item in a different library"); 1157 return; 1158 } 1159 assert.fail("addRelatedItem() allowed for an item in a different library"); 1160 }) 1161 }) 1162 1163 describe("#save()", function () { 1164 it("should throw an error for an empty item without an item type", function* () { 1165 var item = new Zotero.Item; 1166 var e = yield getPromiseError(item.saveTx()); 1167 assert.ok(e); 1168 assert.equal(e.message, "Item type must be set before saving"); 1169 }) 1170 1171 it("should reload child items for parent items", function* () { 1172 var item = yield createDataObject('item'); 1173 var attachment = yield importFileAttachment('test.png', { parentItemID: item.id }); 1174 var note1 = new Zotero.Item('note'); 1175 note1.parentItemID = item.id; 1176 yield note1.saveTx(); 1177 var note2 = new Zotero.Item('note'); 1178 note2.parentItemID = item.id; 1179 yield note2.saveTx(); 1180 1181 assert.lengthOf(item.getAttachments(), 1); 1182 assert.lengthOf(item.getNotes(), 2); 1183 1184 note2.parentItemID = null; 1185 yield note2.saveTx(); 1186 1187 assert.lengthOf(item.getAttachments(), 1); 1188 assert.lengthOf(item.getNotes(), 1); 1189 }); 1190 }) 1191 1192 1193 describe("#_eraseData()", function () { 1194 it("should remove relations pointing to this item", function* () { 1195 var item1 = yield createDataObject('item'); 1196 var item2 = yield createDataObject('item'); 1197 item1.addRelatedItem(item2); 1198 yield item1.saveTx(); 1199 item2.addRelatedItem(item1); 1200 yield item2.saveTx(); 1201 1202 yield item1.eraseTx(); 1203 1204 assert.lengthOf(item2.relatedItems, 0); 1205 yield assert.eventually.equal( 1206 Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM itemRelations WHERE itemID=?", item2.id), 1207 0 1208 ); 1209 }); 1210 }); 1211 1212 1213 describe("#multiDiff", function () { 1214 it("should return set of alternatives for differing fields in other items", function* () { 1215 var type = 'item'; 1216 1217 var dates = ['2016-03-08 17:44:45']; 1218 var accessDates = ['2016-03-08T18:44:45Z']; 1219 var urls = ['http://www.example.com', 'http://example.net']; 1220 1221 var obj1 = createUnsavedDataObject(type); 1222 obj1.setField('date', '2016-03-07 12:34:56'); // different in 1 and 3, not in 2 1223 obj1.setField('url', 'http://example.com'); // different in all three 1224 obj1.setField('title', 'Test'); // only in 1 1225 1226 var obj2 = createUnsavedDataObject(type); 1227 obj2.setField('url', urls[0]); 1228 obj2.setField('accessDate', accessDates[0]); // only in 2 1229 1230 var obj3 = createUnsavedDataObject(type); 1231 obj3.setField('date', dates[0]); 1232 obj3.setField('url', urls[1]); 1233 1234 var alternatives = obj1.multiDiff([obj2, obj3]); 1235 1236 assert.sameMembers(Object.keys(alternatives), ['url', 'date', 'accessDate']); 1237 assert.sameMembers(alternatives.url, urls); 1238 assert.sameMembers(alternatives.date, dates); 1239 assert.sameMembers(alternatives.accessDate, accessDates); 1240 }); 1241 }); 1242 1243 1244 describe("#clone()", function () { 1245 // TODO: Expand to other data 1246 it("should copy creators", function* () { 1247 var item = new Zotero.Item('book'); 1248 item.setCreators([ 1249 { 1250 firstName: "A", 1251 lastName: "Test", 1252 creatorType: 'author' 1253 } 1254 ]); 1255 yield item.saveTx(); 1256 var newItem = item.clone(); 1257 assert.sameDeepMembers(item.getCreators(), newItem.getCreators()); 1258 }) 1259 }) 1260 1261 describe("#moveToLibrary()", function () { 1262 it("should move items from My Library to a filesEditable group", async function () { 1263 var group = await createGroup(); 1264 1265 var item = await createDataObject('item'); 1266 var attachment1 = await importFileAttachment('test.png', { parentID: item.id }); 1267 var file = getTestDataDirectory(); 1268 file.append('test.png'); 1269 var attachment2 = await Zotero.Attachments.linkFromFile({ 1270 file, 1271 parentItemID: item.id 1272 }); 1273 var note = await createDataObject('item', { itemType: 'note', parentID: item.id }); 1274 1275 var originalIDs = [item.id, attachment1.id, attachment2.id, note.id]; 1276 var originalAttachmentFile = attachment1.getFilePath(); 1277 var originalAttachmentHash = await attachment1.attachmentHash 1278 1279 assert.isTrue(await OS.File.exists(originalAttachmentFile)); 1280 1281 var newItem = await item.moveToLibrary(group.libraryID); 1282 1283 // Old items and file should be gone 1284 assert.isTrue(originalIDs.every(id => !Zotero.Items.get(id))); 1285 assert.isFalse(await OS.File.exists(originalAttachmentFile)); 1286 1287 // New items and stored file should exist; linked file should be gone 1288 assert.equal(newItem.libraryID, group.libraryID); 1289 assert.lengthOf(newItem.getAttachments(), 1); 1290 var newAttachment = Zotero.Items.get(newItem.getAttachments()[0]); 1291 assert.equal(await newAttachment.attachmentHash, originalAttachmentHash); 1292 assert.lengthOf(newItem.getNotes(), 1); 1293 }); 1294 1295 it("should move items from My Library to a non-filesEditable group", async function () { 1296 var group = await createGroup({ 1297 filesEditable: false 1298 }); 1299 1300 var item = await createDataObject('item'); 1301 var attachment = await importFileAttachment('test.png', { parentID: item.id }); 1302 1303 var originalIDs = [item.id, attachment.id]; 1304 var originalAttachmentFile = attachment.getFilePath(); 1305 var originalAttachmentHash = await attachment.attachmentHash 1306 1307 assert.isTrue(await OS.File.exists(originalAttachmentFile)); 1308 1309 var newItem = await item.moveToLibrary(group.libraryID); 1310 1311 // Old items and file should be gone 1312 assert.isTrue(originalIDs.every(id => !Zotero.Items.get(id))); 1313 assert.isFalse(await OS.File.exists(originalAttachmentFile)); 1314 1315 // Parent should exist, but attachment should not 1316 assert.equal(newItem.libraryID, group.libraryID); 1317 assert.lengthOf(newItem.getAttachments(), 0); 1318 }); 1319 }); 1320 1321 describe("#toJSON()", function () { 1322 describe("default mode", function () { 1323 it("should output only fields with values", function* () { 1324 var itemType = "book"; 1325 var title = "Test"; 1326 1327 var item = new Zotero.Item(itemType); 1328 item.setField("title", title); 1329 var id = yield item.saveTx(); 1330 item = Zotero.Items.get(id); 1331 var json = item.toJSON(); 1332 1333 assert.equal(json.itemType, itemType); 1334 assert.equal(json.title, title); 1335 assert.isUndefined(json.date); 1336 assert.isUndefined(json.numPages); 1337 }) 1338 1339 it("should output 'deleted' as 1", function* () { 1340 var itemType = "book"; 1341 var title = "Test"; 1342 1343 var item = new Zotero.Item(itemType); 1344 item.setField("title", title); 1345 item.deleted = true; 1346 var id = yield item.saveTx(); 1347 item = Zotero.Items.get(id); 1348 var json = item.toJSON(); 1349 1350 assert.strictEqual(json.deleted, 1); 1351 }) 1352 1353 it.skip("should output attachment fields from file", function* () { 1354 var file = getTestDataDirectory(); 1355 file.append('test.png'); 1356 var item = yield Zotero.Attachments.importFromFile({ file }); 1357 1358 yield Zotero.DB.executeTransaction(function* () { 1359 yield Zotero.Sync.Storage.Local.setSyncedModificationTime( 1360 item.id, new Date().getTime() 1361 ); 1362 yield Zotero.Sync.Storage.Local.setSyncedHash( 1363 item.id, 'b32e33f529942d73bea4ed112310f804' 1364 ); 1365 }); 1366 1367 var json = item.toJSON(); 1368 assert.equal(json.linkMode, 'imported_file'); 1369 assert.equal(json.filename, 'test.png'); 1370 assert.isUndefined(json.path); 1371 assert.equal(json.mtime, (yield item.attachmentModificationTime)); 1372 assert.equal(json.md5, (yield item.attachmentHash)); 1373 }) 1374 1375 it("should omit storage values with .skipStorageProperties", function* () { 1376 var file = getTestDataDirectory(); 1377 file.append('test.png'); 1378 var item = yield Zotero.Attachments.importFromFile({ file }); 1379 1380 item.attachmentSyncedModificationTime = new Date().getTime(); 1381 item.attachmentSyncedHash = 'b32e33f529942d73bea4ed112310f804'; 1382 yield item.saveTx({ skipAll: true }); 1383 1384 var json = item.toJSON({ 1385 skipStorageProperties: true 1386 }); 1387 assert.isUndefined(json.mtime); 1388 assert.isUndefined(json.md5); 1389 }); 1390 1391 it("should output synced storage values with .syncedStorageProperties", function* () { 1392 var item = new Zotero.Item('attachment'); 1393 item.attachmentLinkMode = 'imported_file'; 1394 item.fileName = 'test.txt'; 1395 yield item.saveTx(); 1396 1397 var mtime = new Date().getTime(); 1398 var md5 = 'b32e33f529942d73bea4ed112310f804'; 1399 1400 item.attachmentSyncedModificationTime = mtime; 1401 item.attachmentSyncedHash = md5; 1402 yield item.saveTx({ skipAll: true }); 1403 1404 var json = item.toJSON({ 1405 syncedStorageProperties: true 1406 }); 1407 assert.equal(json.mtime, mtime); 1408 assert.equal(json.md5, md5); 1409 }) 1410 1411 it.skip("should output unset storage properties as null", function* () { 1412 var item = new Zotero.Item('attachment'); 1413 item.attachmentLinkMode = 'imported_file'; 1414 item.fileName = 'test.txt'; 1415 var id = yield item.saveTx(); 1416 var json = item.toJSON(); 1417 1418 assert.isNull(json.mtime); 1419 assert.isNull(json.md5); 1420 }) 1421 1422 it("shouldn't include filename or path for linked_url attachments", function* () { 1423 var item = new Zotero.Item('attachment'); 1424 item.attachmentLinkMode = 'linked_url'; 1425 item.url = "https://www.zotero.org/"; 1426 var json = item.toJSON(); 1427 assert.notProperty(json, "filename"); 1428 assert.notProperty(json, "path"); 1429 }); 1430 1431 it("should include inPublications=true for items in My Publications", function* () { 1432 var item = createUnsavedDataObject('item'); 1433 item.inPublications = true; 1434 var json = item.toJSON(); 1435 assert.propertyVal(json, "inPublications", true); 1436 }); 1437 1438 it("shouldn't include inPublications for items not in My Publications in patch mode", function* () { 1439 var item = createUnsavedDataObject('item'); 1440 var json = item.toJSON(); 1441 assert.notProperty(json, "inPublications"); 1442 }); 1443 1444 it("should include inPublications=false for personal-library items not in My Publications in full mode", async function () { 1445 var item = createUnsavedDataObject('item', { libraryID: Zotero.Libraries.userLibraryID }); 1446 var json = item.toJSON({ mode: 'full' }); 1447 assert.property(json, "inPublications", false); 1448 }); 1449 1450 it("shouldn't include inPublications=false for group items not in My Publications in full mode", function* () { 1451 var group = yield getGroup(); 1452 var item = createUnsavedDataObject('item', { libraryID: group.libraryID }); 1453 var json = item.toJSON({ mode: 'full' }); 1454 assert.notProperty(json, "inPublications"); 1455 }); 1456 }) 1457 1458 describe("'full' mode", function () { 1459 it("should output all fields", function* () { 1460 var itemType = "book"; 1461 var title = "Test"; 1462 1463 var item = new Zotero.Item(itemType); 1464 item.setField("title", title); 1465 var id = yield item.saveTx(); 1466 item = yield Zotero.Items.getAsync(id); 1467 var json = item.toJSON({ mode: 'full' }); 1468 assert.equal(json.title, title); 1469 assert.equal(json.date, ""); 1470 assert.equal(json.numPages, ""); 1471 }) 1472 }) 1473 1474 describe("'patch' mode", function () { 1475 it("should output only fields that differ", function* () { 1476 var itemType = "book"; 1477 var title = "Test"; 1478 var date = "2015-05-12"; 1479 1480 var item = new Zotero.Item(itemType); 1481 item.setField("title", title); 1482 var id = yield item.saveTx(); 1483 item = yield Zotero.Items.getAsync(id); 1484 var patchBase = item.toJSON(); 1485 1486 item.setField("date", date); 1487 yield item.saveTx(); 1488 var json = item.toJSON({ 1489 patchBase: patchBase 1490 }) 1491 assert.isUndefined(json.itemType); 1492 assert.isUndefined(json.title); 1493 assert.equal(json.date, date); 1494 assert.isUndefined(json.numPages); 1495 assert.isUndefined(json.deleted); 1496 assert.isUndefined(json.creators); 1497 assert.isUndefined(json.relations); 1498 assert.isUndefined(json.tags); 1499 }) 1500 1501 it("should include changed 'deleted' field", function* () { 1502 // True to false 1503 var item = new Zotero.Item('book'); 1504 item.deleted = true; 1505 var id = yield item.saveTx(); 1506 item = yield Zotero.Items.getAsync(id); 1507 var patchBase = item.toJSON(); 1508 1509 item.deleted = false; 1510 var json = item.toJSON({ 1511 patchBase: patchBase 1512 }) 1513 assert.isUndefined(json.title); 1514 assert.isFalse(json.deleted); 1515 1516 // False to true 1517 var item = new Zotero.Item('book'); 1518 item.deleted = false; 1519 var id = yield item.saveTx(); 1520 item = yield Zotero.Items.getAsync(id); 1521 var patchBase = item.toJSON(); 1522 1523 item.deleted = true; 1524 var json = item.toJSON({ 1525 patchBase: patchBase 1526 }) 1527 assert.isUndefined(json.title); 1528 assert.strictEqual(json.deleted, 1); 1529 }) 1530 1531 it("should set 'parentItem' to false when cleared", function* () { 1532 var item = yield createDataObject('item'); 1533 var note = new Zotero.Item('note'); 1534 note.parentID = item.id; 1535 // Create initial JSON with parentItem 1536 var patchBase = note.toJSON(); 1537 // Clear parent item and regenerate JSON 1538 note.parentID = false; 1539 var json = note.toJSON({ patchBase }); 1540 assert.isFalse(json.parentItem); 1541 }); 1542 1543 it("should include relations if related item was removed", function* () { 1544 var item1 = yield createDataObject('item'); 1545 var item2 = yield createDataObject('item'); 1546 var item3 = yield createDataObject('item'); 1547 var item4 = yield createDataObject('item'); 1548 1549 var relateItems = Zotero.Promise.coroutine(function* (i1, i2) { 1550 yield Zotero.DB.executeTransaction(function* () { 1551 i1.addRelatedItem(i2); 1552 yield i1.save({ 1553 skipDateModifiedUpdate: true 1554 }); 1555 i2.addRelatedItem(i1); 1556 yield i2.save({ 1557 skipDateModifiedUpdate: true 1558 }); 1559 }); 1560 }); 1561 1562 yield relateItems(item1, item2); 1563 yield relateItems(item1, item3); 1564 yield relateItems(item1, item4); 1565 1566 var patchBase = item1.toJSON(); 1567 1568 item1.removeRelatedItem(item2); 1569 yield item1.saveTx(); 1570 item2.removeRelatedItem(item1); 1571 yield item2.saveTx(); 1572 1573 var json = item1.toJSON({ patchBase }); 1574 assert.sameMembers(json.relations['dc:relation'], item1.getRelations()['dc:relation']); 1575 }); 1576 1577 it("shouldn't clear storage properties from original in .skipStorageProperties mode", function* () { 1578 var item = new Zotero.Item('attachment'); 1579 item.attachmentLinkMode = 'imported_file'; 1580 item.attachmentFilename = 'test.txt'; 1581 item.attachmentContentType = 'text/plain'; 1582 item.attachmentCharset = 'utf-8'; 1583 item.attachmentSyncedModificationTime = 1234567890000; 1584 item.attachmentSyncedHash = '18d21750c8abd5e3afa8ea89e3dfa570'; 1585 var patchBase = item.toJSON({ 1586 syncedStorageProperties: true 1587 }); 1588 item.setNote("Test"); 1589 var json = item.toJSON({ 1590 patchBase, 1591 skipStorageProperties: true 1592 }); 1593 Zotero.debug(json); 1594 assert.equal(json.note, "Test"); 1595 assert.notProperty(json, "md5"); 1596 assert.notProperty(json, "mtime"); 1597 }); 1598 }) 1599 }) 1600 1601 describe("#fromJSON()", function () { 1602 it("should clear missing fields", function* () { 1603 var item = new Zotero.Item('book'); 1604 item.setField('title', 'Test'); 1605 item.setField('date', '2016'); 1606 item.setField('accessDate', '2015-06-07T20:56:00Z'); 1607 yield item.saveTx(); 1608 var json = item.toJSON(); 1609 // Remove fields, which should cause them to be cleared in fromJSON() 1610 delete json.date; 1611 delete json.accessDate; 1612 1613 item.fromJSON(json); 1614 assert.strictEqual(item.getField('title'), 'Test'); 1615 assert.strictEqual(item.getField('date'), ''); 1616 assert.strictEqual(item.getField('accessDate'), ''); 1617 }); 1618 1619 it("should remove item from collection if 'collections' property not provided", function* () { 1620 var collection = yield createDataObject('collection'); 1621 // Create standalone attachment in collection 1622 var attachment = yield importFileAttachment('test.png', { collections: [collection.id] }); 1623 var item = yield createDataObject('item', { collections: [collection.id] }); 1624 1625 assert.isTrue(collection.hasItem(attachment.id)); 1626 var json = attachment.toJSON(); 1627 json.path = 'storage:test2.png'; 1628 // Add to parent, which implicitly removes from collection 1629 json.parentItem = item.key; 1630 delete json.collections; 1631 attachment.fromJSON(json); 1632 yield attachment.saveTx(); 1633 assert.isFalse(collection.hasItem(attachment.id)); 1634 }); 1635 1636 it("should remove child item from parent if 'parentKey' property not provided", async function () { 1637 var item = await createDataObject('item'); 1638 var note = await createDataObject('item', { itemType: 'note', parentKey: [item.key] }); 1639 1640 var json = note.toJSON(); 1641 delete json.parentItem; 1642 1643 note.fromJSON(json); 1644 await note.saveTx(); 1645 1646 assert.lengthOf(item.getNotes(), 0); 1647 }); 1648 1649 it("should remove item from trash if 'deleted' property not provided", async function () { 1650 var item = await createDataObject('item', { deleted: true }); 1651 1652 assert.isTrue(item.deleted); 1653 1654 var json = item.toJSON(); 1655 delete json.deleted; 1656 1657 item.fromJSON(json); 1658 await item.saveTx(); 1659 1660 assert.isFalse(item.deleted); 1661 }); 1662 1663 it("should remove item from My Publications if 'inPublications' property not provided", async function () { 1664 var item = await createDataObject('item', { inPublications: true }); 1665 1666 assert.isTrue(item.inPublications); 1667 1668 var json = item.toJSON(); 1669 delete json.inPublications; 1670 1671 item.fromJSON(json); 1672 await item.saveTx(); 1673 1674 assert.isFalse(item.inPublications); 1675 }); 1676 1677 it("should ignore unknown fields", function* () { 1678 var json = { 1679 itemType: "journalArticle", 1680 title: "Test", 1681 foo: "Invalid" 1682 }; 1683 var item = new Zotero.Item; 1684 item.fromJSON(json); 1685 assert.equal(item.getField('title'), 'Test'); 1686 }) 1687 1688 it("should accept ISO 8601 dates", function* () { 1689 var json = { 1690 itemType: "journalArticle", 1691 accessDate: "2015-06-07T20:56:00Z", 1692 dateAdded: "2015-06-07T20:57:00Z", 1693 dateModified: "2015-06-07T20:58:00Z", 1694 }; 1695 var item = new Zotero.Item; 1696 item.fromJSON(json); 1697 assert.equal(item.getField('accessDate'), '2015-06-07 20:56:00'); 1698 assert.equal(item.dateAdded, '2015-06-07 20:57:00'); 1699 assert.equal(item.dateModified, '2015-06-07 20:58:00'); 1700 }) 1701 1702 it("should accept ISO 8601 access date without time", function* () { 1703 var json = { 1704 itemType: "journalArticle", 1705 accessDate: "2015-06-07", 1706 dateAdded: "2015-06-07T20:57:00Z", 1707 dateModified: "2015-06-07T20:58:00Z", 1708 }; 1709 var item = new Zotero.Item; 1710 item.fromJSON(json); 1711 assert.equal(item.getField('accessDate'), '2015-06-07'); 1712 assert.equal(item.dateAdded, '2015-06-07 20:57:00'); 1713 assert.equal(item.dateModified, '2015-06-07 20:58:00'); 1714 }) 1715 1716 it("should ignore non–ISO 8601 dates", function* () { 1717 var json = { 1718 itemType: "journalArticle", 1719 accessDate: "2015-06-07 20:56:00", 1720 dateAdded: "2015-06-07 20:57:00", 1721 dateModified: "2015-06-07 20:58:00", 1722 }; 1723 var item = new Zotero.Item; 1724 item.fromJSON(json); 1725 assert.strictEqual(item.getField('accessDate'), ''); 1726 // DEBUG: Should these be null, or empty string like other fields from getField()? 1727 assert.isNull(item.dateAdded); 1728 assert.isNull(item.dateModified); 1729 }) 1730 1731 it("should set creators", function* () { 1732 var json = { 1733 itemType: "journalArticle", 1734 creators: [ 1735 { 1736 firstName: "First", 1737 lastName: "Last", 1738 creatorType: "author" 1739 }, 1740 { 1741 name: "Test Name", 1742 creatorType: "editor" 1743 } 1744 ] 1745 }; 1746 1747 var item = new Zotero.Item; 1748 item.fromJSON(json); 1749 var id = yield item.saveTx(); 1750 assert.sameDeepMembers(item.getCreatorsJSON(), json.creators); 1751 }) 1752 1753 it("should map a base field to an item-specific field", function* () { 1754 var item = new Zotero.Item("bookSection"); 1755 item.fromJSON({ 1756 "itemType":"bookSection", 1757 "publicationTitle":"Publication Title" 1758 }); 1759 assert.equal(item.getField("bookTitle"), "Publication Title"); 1760 }); 1761 }); 1762 });