From 6f1833f9360a43346df5f2d883d617bfa9ec66c8 Mon Sep 17 00:00:00 2001
From: Dan Stillman <dstillman@zotero.org>
Date: Fri, 13 Apr 2018 23:36:09 -0400
Subject: [PATCH] Remove items from trash and My Publications when removed via
 sync

Zotero.Item::fromJSON() wasn't properly accounting for missing 'deleted'
or 'inPublications' properties.
---
 chrome/content/zotero/xpcom/data/item.js | 16 ++++----
 test/tests/itemTest.js                   | 48 ++++++++++++++++++++++--
 2 files changed, 52 insertions(+), 12 deletions(-)

diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
index e66a48cdb3..bdcbdd6a70 100644
--- a/chrome/content/zotero/xpcom/data/item.js
+++ b/chrome/content/zotero/xpcom/data/item.js
@@ -4224,15 +4224,6 @@ Zotero.Item.prototype.fromJSON = function (json) {
 			this[field] = val;
 			break;
 		
-		case 'parentItem':
-			this.parentKey = val;
-			break;
-		
-		case 'deleted':
-		case 'inPublications':
-			this[field] = !!val;
-			break;
-		
 		case 'creators':
 			this.setCreators(json.creators);
 			break;
@@ -4320,6 +4311,13 @@ Zotero.Item.prototype.fromJSON = function (json) {
 		let note = json.note;
 		this.setNote(note !== undefined ? note : "");
 	}
+	
+	// Update boolean fields that might not be present in JSON
+	['deleted', 'inPublications'].forEach(field => {
+		if (json[field] || this[field]) {
+			this[field] = !!json[field];
+		}
+	});
 }
 
 
diff --git a/test/tests/itemTest.js b/test/tests/itemTest.js
index 63885fd052..567d75bdaf 100644
--- a/test/tests/itemTest.js
+++ b/test/tests/itemTest.js
@@ -1616,20 +1616,62 @@ describe("Zotero.Item", function () {
 			assert.strictEqual(item.getField('accessDate'), '');
 		});
 		
-		it("should remove child item from collection if 'collections' property not provided", function* () {
+		it("should remove item from collection if 'collections' property not provided", function* () {
 			var collection = yield createDataObject('collection');
 			// Create standalone attachment in collection
 			var attachment = yield importFileAttachment('test.png', { collections: [collection.id] });
 			var item = yield createDataObject('item', { collections: [collection.id] });
 			
+			assert.isTrue(collection.hasItem(attachment.id));
 			var json = attachment.toJSON();
 			json.path = 'storage:test2.png';
 			// Add to parent, which implicitly removes from collection
 			json.parentItem = item.key;
 			delete json.collections;
-			Zotero.debug(json);
 			attachment.fromJSON(json);
-			yield attachment.save();
+			yield attachment.saveTx();
+			assert.isFalse(collection.hasItem(attachment.id));
+		});
+		
+		it("should remove child item from parent if 'parentKey' property not provided", async function () {
+			var item = await createDataObject('item');
+			var note = await createDataObject('item', { itemType: 'note', parentKey: [item.key] });
+			
+			var json = note.toJSON();
+			delete json.parentItem;
+			
+			note.fromJSON(json);
+			await note.saveTx();
+			
+			assert.lengthOf(item.getNotes(), 0);
+		});
+		
+		it("should remove item from trash if 'deleted' property not provided", async function () {
+			var item = await createDataObject('item', { deleted: true });
+			
+			assert.isTrue(item.deleted);
+			
+			var json = item.toJSON();
+			delete json.deleted;
+			
+			item.fromJSON(json);
+			await item.saveTx();
+			
+			assert.isFalse(item.deleted);
+		});
+		
+		it("should remove item from My Publications if 'inPublications' property not provided", async function () {
+			var item = await createDataObject('item', { inPublications: true });
+			
+			assert.isTrue(item.inPublications);
+			
+			var json = item.toJSON();
+			delete json.inPublications;
+			
+			item.fromJSON(json);
+			await item.saveTx();
+			
+			assert.isFalse(item.inPublications);
 		});
 		
 		it("should ignore unknown fields", function* () {