diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 19248dc042..5f41236157 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -441,10 +441,13 @@ Zotero.CollectionTreeView.prototype.notify = Zotero.Promise.coroutine(function* let row; let id = ids[0]; let rowID = "C" + id; + let selectedIndex = this.selection.count ? this.selection.currentIndex : 0; switch (type) { case 'collection': + let collection = Zotero.Collections.get(id); row = this.getRowIndexByID(rowID); + // If collection is visible if (row !== false) { // TODO: Only move if name changed let reopen = this.isContainerOpen(row); @@ -452,24 +455,52 @@ Zotero.CollectionTreeView.prototype.notify = Zotero.Promise.coroutine(function* this._closeContainer(row); } this._removeRow(row); - yield this._addSortedRow('collection', id); - yield this.selectByID(currentTreeRow.id); - if (reopen) { - let newRow = this.getRowIndexByID(rowID); - if (!this.isContainerOpen(newRow)) { - yield this.toggleOpenState(newRow); + + // Collection was moved to trash, so don't add it back + if (collection.deleted) { + this._refreshRowMap(); + this.selectAfterRowRemoval(selectedIndex); + } + else { + yield this._addSortedRow('collection', id); + yield this.selectByID(currentTreeRow.id); + if (reopen) { + let newRow = this.getRowIndexByID(rowID); + if (!this.isContainerOpen(newRow)) { + yield this.toggleOpenState(newRow); + } } } } + // If collection isn't currently visible and it isn't in the trash (because it was + // undeleted), add it (if possible without opening any containers) + else if (!collection.deleted) { + yield this._addSortedRow('collection', id); + // Invalidate parent in case it's become non-empty + let parentRow = this.getRowIndexByID("C" + collection.parentID); + if (parentRow !== false) { + this._treebox.invalidateRow(parentRow); + } + } break; case 'search': + let search = Zotero.Searches.get(id); row = this.getRowIndexByID("S" + id); if (row !== false) { // TODO: Only move if name changed this._removeRow(row); - yield this._addSortedRow('search', id); - yield this.selectByID(currentTreeRow.id); + + // Search moved to trash + if (search.deleted) { + this._refreshRowMap(); + this.selectAfterRowRemoval(selectedIndex); + } + // If search isn't in trash, add it back + else { + yield this._addSortedRow('search', id); + yield this.selectByID(currentTreeRow.id); + } } break; @@ -1381,6 +1412,9 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi // Add collections for (var i = 0, len = collections.length; i < len; i++) { + // Skip collections in trash + if (collections[i].deleted) continue; + let beforeRow = row + 1 + newRows; this._addRowToArray( rows, diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index a8eb838160..d5f583db7b 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -153,6 +153,7 @@ Zotero.Collection.prototype.loadFromRow = function(row) { // Boolean case 'synced': + case 'deleted': case 'hasChildCollections': case 'hasChildItems': val = !!val; @@ -174,9 +175,15 @@ Zotero.Collection.prototype.loadFromRow = function(row) { } -Zotero.Collection.prototype.hasChildCollections = function() { +Zotero.Collection.prototype.hasChildCollections = function (includeTrashed) { this._requireData('childCollections'); - return this._childCollections.size > 0; + if (!this._childCollections.size) { + return false; + } + if (includeTrashed) { + return this._childCollections.size > 0; + } + return !this.getChildCollections().every(c => c.deleted); } Zotero.Collection.prototype.hasChildItems = function() { @@ -328,6 +335,19 @@ Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) }.bind(this)); } } + + if (this._changedData.deleted !== undefined) { + if (this._changedData.deleted) { + sql = "REPLACE INTO deletedCollections (collectionID) VALUES (?)"; + } + else { + sql = "DELETE FROM deletedCollections WHERE collectionID=?"; + } + yield Zotero.DB.queryAsync(sql, collectionID); + + this._clearChanged('deleted'); + this._markForReload('primaryData'); + } }); Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { @@ -709,6 +729,7 @@ Zotero.Collection.prototype.fromJSON = function (json, options = {}) { case 'name': case 'parentCollection': case 'relations': + case 'deleted': break; default: @@ -726,6 +747,10 @@ Zotero.Collection.prototype.fromJSON = function (json, options = {}) { this.parentKey = json.parentCollection ? json.parentCollection : false; this.setRelations(json.relations || {}); + + if (json.deleted || this.deleted) { + this.deleted = !!json.deleted; + } } diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js index 857161b7d4..4e0d0c94b4 100644 --- a/chrome/content/zotero/xpcom/data/collections.js +++ b/chrome/content/zotero/xpcom/data/collections.js @@ -40,6 +40,8 @@ Zotero.Collections = function() { version: "O.version", synced: "O.synced", + deleted: "DC.collectionID IS NOT NULL AS deleted", + parentID: "O.parentCollectionID AS parentID", parentKey: "CP.key AS parentKey", @@ -51,7 +53,8 @@ Zotero.Collections = function() { this._primaryDataSQLFrom = "FROM collections O " - + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID)"; + + "LEFT JOIN deletedCollections DC ON (O.collectionID=DC.collectionID)" + + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID) "; this._relationsTable = "collectionRelations"; diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index c7c8d04074..ba9f7fe3e5 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -49,6 +49,7 @@ Zotero.DataObject = function () { this._identified = false; this._parentID = null; this._parentKey = null; + this._deleted = null; this._relations = []; @@ -98,6 +99,33 @@ Zotero.defineProperty(Zotero.DataObject.prototype, '_canHaveParent', { value: true }); +// Define boolean properties +for (let name of ['deleted']) { + let prop = '_' + name; + Zotero.defineProperty(Zotero.DataObject.prototype, name, { + get: function() { + if (!this.id) { + return false; + } + var val = this._getLatestField(name); + if (this[prop] !== null) { + return this[prop]; + } + this._requireData('primaryData'); + }, + set: function(val) { + val = !!val; + var oldVal = this._getLatestField(name); + if (oldVal == val) { + Zotero.debug(Zotero.Utilities.capitalize(name) + + ` state hasn't changed for ${this._objectType} ${this.id}`); + return; + } + this._markFieldChange(name, val); + } + }); +} + Zotero.defineProperty(Zotero.DataObject.prototype, 'ObjectsClass', { get: function() { return this._ObjectsClass; } }); @@ -721,7 +749,7 @@ Zotero.DataObject.prototype._getLatestField = function (field) { */ Zotero.DataObject.prototype._markFieldChange = function (field, value) { // New method (changedData) - if (field == 'tags') { + if (['deleted', 'tags'].includes(field)) { if (Array.isArray(value)) { this._changedData[field] = [...value]; } @@ -1008,6 +1036,13 @@ Zotero.DataObject.prototype._saveData = function (env) { env.sqlColumns.push('clientDateModified'); env.sqlValues.push(Zotero.DB.transactionDateTime); } + + if (!env.options.skipNotifier && this._changedData.deleted !== undefined) { + Zotero.Notifier.queue('refresh', 'trash', this.libraryID, {}, env.options.notifierQueue); + if (!env.isNew && this._changedData.deleted) { + Zotero.Notifier.queue('trash', this._objectType, this.id, {}, env.options.notifierQueue); + } + } }; Zotero.DataObject.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { @@ -1291,6 +1326,11 @@ Zotero.DataObject.prototype._preToJSON = function (options) { } Zotero.DataObject.prototype._postToJSON = function (env) { + var deleted = this._getLatestField('deleted'); + if (deleted || env.options.mode == 'full') { + env.obj.deleted = !!deleted; + } + if (env.mode == 'patch') { env.obj = Zotero.DataObjectUtilities.patch(env.options.patchBase, env.obj); } diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js index 977f434b9c..b0bca903a3 100644 --- a/chrome/content/zotero/xpcom/data/dataObjects.js +++ b/chrome/content/zotero/xpcom/data/dataObjects.js @@ -231,6 +231,40 @@ Zotero.DataObjects.prototype.getLoaded = function () { } +/** + * Return objects in the trash + * + * @param {Integer} libraryID - Library to search + * @param {Boolean} [asIDs] - Return object ids instead of objects + * @param {Integer} [days] + * @param {Integer} [limit] + * @return {Promise} + */ +Zotero.DataObjects.prototype.getDeleted = async function (libraryID, asIDs, days, limit) { + var sql = `SELECT ${this._ZDO_id} FROM ${this._ZDO_table} ` + + `JOIN deleted${this._ZDO_Objects} USING (${this._ZDO_id}) ` + + "WHERE libraryID=?"; + var params = [libraryID]; + if (days) { + sql += " AND dateDeleted <= DATE('NOW', '-" + parseInt(days) + " DAYS')"; + } + if (limit) { + sql += " LIMIT ?"; + params.push(limit); + } + var ids = await Zotero.DB.columnQueryAsync(sql, params); + if (!ids.length) { + return []; + } + if (asIDs) { + return ids; + } + return this.getAsync(ids); +}; + + + + Zotero.DataObjects.prototype.getAllIDs = function (libraryID) { var sql = `SELECT ${this._ZDO_id} FROM ${this._ZDO_table} WHERE libraryID=?`; return Zotero.DB.columnQueryAsync(sql, [libraryID]); diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 3c44122b67..43b763a4c2 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -68,7 +68,6 @@ Zotero.Item = function(itemTypeOrID) { this._bestAttachmentState = null; this._fileExists = null; - this._deleted = null; this._hasNote = null; this._noteAccessTime = null; @@ -1131,7 +1130,7 @@ Zotero.Item.prototype.removeCreator = function(orderIndex, allowMissing) { // Define boolean properties -for (let name of ['deleted', 'inPublications']) { +for (let name of ['inPublications']) { let prop = '_' + name; Zotero.defineProperty(Zotero.Item.prototype, name, { get: function() { @@ -1517,8 +1516,8 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { } // Trashed status - if (this._changed.deleted) { - if (this._deleted) { + if (this._changedData.deleted !== undefined) { + if (this._changedData.deleted) { sql = "REPLACE INTO deletedItems (itemID) VALUES (?)"; } else { @@ -1551,7 +1550,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { // Refresh trash if (!env.options.skipNotifier) { Zotero.Notifier.queue('refresh', 'trash', this.libraryID, {}, env.options.notifierQueue); - if (this._deleted) { + if (this._changedData.deleted) { Zotero.Notifier.queue('trash', 'item', this.id, {}, env.options.notifierQueue); } } @@ -1559,6 +1558,9 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { if (parentItemID) { reloadParentChildItems[parentItemID] = true; } + + this._clearChanged('deleted'); + this._markForReload('primaryData'); } if (this._changed.inPublications) { @@ -3666,8 +3668,8 @@ Zotero.DataObject.prototype.setDeleted = Zotero.Promise.coroutine(function* (del this._deleted = !!deleted; - if (this._changed.deleted) { - delete this._changed.deleted; + if (this._changedData.deleted !== undefined) { + delete this._changedData.deleted; } }); @@ -4636,13 +4638,6 @@ Zotero.Item.prototype.toJSON = function (options = {}) { obj.inPublications = this._inPublications; } - // Deleted - let deleted = this.deleted; - if (deleted || mode == 'full') { - // Match what APIv3 returns, though it would be good to change this - obj.deleted = deleted ? 1 : 0; - } - // Relations obj.relations = this.getRelations(); diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index e2cfa7a273..bfbf478dd1 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -94,30 +94,6 @@ Zotero.Items = function() { }); - /** - * Return items marked as deleted - * - * @param {Integer} libraryID - Library to search - * @param {Boolean} [asIDs] - Return itemIDs instead of Zotero.Item objects - * @return {Promise} - */ - this.getDeleted = Zotero.Promise.coroutine(function* (libraryID, asIDs, days) { - var sql = "SELECT itemID FROM items JOIN deletedItems USING (itemID) " - + "WHERE libraryID=?"; - if (days) { - sql += " AND dateDeleted<=DATE('NOW', '-" + parseInt(days) + " DAYS')"; - } - var ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]); - if (!ids.length) { - return []; - } - if (asIDs) { - return ids; - } - return this.getAsync(ids); - }); - - /** * Returns all items in a given library * diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js index 3463669584..eca9b4aa43 100644 --- a/chrome/content/zotero/xpcom/data/search.js +++ b/chrome/content/zotero/xpcom/data/search.js @@ -136,6 +136,7 @@ Zotero.Search.prototype.loadFromRow = function (row) { // Boolean case 'synced': + case 'deleted': val = !!val; break; @@ -219,6 +220,20 @@ Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) { i++; } } + + // Trashed status + if (this._changedData.deleted !== undefined) { + if (this._changedData.deleted) { + sql = "INSERT OR IGNORE INTO deletedSearches (savedSearchID) VALUES (?)"; + } + else { + sql = "DELETE FROM deletedSearches WHERE savedSearchID=?"; + } + yield Zotero.DB.queryAsync(sql, searchID); + + this._clearChanged('deleted'); + this._markForReload('primaryData'); + } }); Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { @@ -844,6 +859,10 @@ Zotero.Search.prototype.fromJSON = function (json, options = {}) { condition.value ); } + + if (json.deleted || this.deleted) { + this.deleted = !!json.deleted; + } } diff --git a/chrome/content/zotero/xpcom/data/searches.js b/chrome/content/zotero/xpcom/data/searches.js index b4c21c41e0..173aa0c593 100644 --- a/chrome/content/zotero/xpcom/data/searches.js +++ b/chrome/content/zotero/xpcom/data/searches.js @@ -36,10 +36,12 @@ Zotero.Searches = function() { libraryID: "O.libraryID", key: "O.key", version: "O.version", - synced: "O.synced" + synced: "O.synced", + deleted: "DS.savedSearchID IS NOT NULL AS deleted", } - this._primaryDataSQLFrom = "FROM savedSearches O"; + this._primaryDataSQLFrom = "FROM savedSearches O " + + "LEFT JOIN deletedSearches DS ON (O.savedSearchID=DS.savedSearchID)"; this.init = Zotero.Promise.coroutine(function* () { yield Zotero.DataObjects.prototype.init.apply(this); diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index e1c0c6f55e..fb65e1688b 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -3193,6 +3193,13 @@ Zotero.Schema = new function(){ yield Zotero.DB.queryAsync("UPDATE itemNotes SET parentItemID=NULL WHERE itemID=parentItemID"); } + else if (i == 111) { + yield Zotero.DB.queryAsync("CREATE TABLE deletedCollections (\n collectionID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL,\n FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE\n)"); + yield Zotero.DB.queryAsync("CREATE INDEX deletedCollections_dateDeleted ON deletedCollections(dateDeleted)"); + yield Zotero.DB.queryAsync("CREATE TABLE deletedSearches (\n savedSearchID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL,\n FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE\n)"); + yield Zotero.DB.queryAsync("CREATE INDEX deletedSearches_dateDeleted ON deletedSearches(dateDeleted)"); + } + // If breaking compatibility or doing anything dangerous, clear minorUpdateFrom } diff --git a/resource/schema/userdata.sql b/resource/schema/userdata.sql index 5af9c569a9..3ddc70f30a 100644 --- a/resource/schema/userdata.sql +++ b/resource/schema/userdata.sql @@ -1,4 +1,4 @@ --- 110 +-- 111 -- Copyright (c) 2009 Center for History and New Media -- George Mason University, Fairfax, Virginia, USA @@ -241,6 +241,13 @@ CREATE TABLE savedSearchConditions ( FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE ); +CREATE TABLE deletedCollections ( + collectionID INTEGER PRIMARY KEY, + dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE +); +CREATE INDEX deletedCollections_dateDeleted ON deletedCollections(dateDeleted); + CREATE TABLE deletedItems ( itemID INTEGER PRIMARY KEY, dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL, @@ -248,6 +255,13 @@ CREATE TABLE deletedItems ( ); CREATE INDEX deletedItems_dateDeleted ON deletedItems(dateDeleted); +CREATE TABLE deletedSearches ( + savedSearchID INTEGER PRIMARY KEY, + dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE +); +CREATE INDEX deletedSearches_dateDeleted ON deletedItems(dateDeleted); + CREATE TABLE libraries ( libraryID INTEGER PRIMARY KEY, type TEXT NOT NULL, diff --git a/test/content/support.js b/test/content/support.js index 6d4379b05b..02ac0796af 100644 --- a/test/content/support.js +++ b/test/content/support.js @@ -431,12 +431,12 @@ function createUnsavedDataObject(objectType, params = {}) { throw new Error("Object type not provided"); } - var allowedParams = ['libraryID', 'parentID', 'parentKey', 'synced', 'version']; + var allowedParams = ['libraryID', 'parentID', 'parentKey', 'synced', 'version', 'deleted']; var itemType; if (objectType == 'item' || objectType == 'feedItem') { itemType = params.itemType || 'book'; - allowedParams.push('deleted', 'dateAdded', 'dateModified'); + allowedParams.push('dateAdded', 'dateModified'); } if (objectType == 'item') { allowedParams.push('inPublications'); diff --git a/test/tests/collectionTest.js b/test/tests/collectionTest.js index ec60aedc38..6304d8a41c 100644 --- a/test/tests/collectionTest.js +++ b/test/tests/collectionTest.js @@ -168,7 +168,31 @@ describe("Zotero.Collection", function() { collection2.parentKey = collection3.key; yield collection2.saveTx(); assert.isFalse(collection1.hasChildCollections()); - }) + }); + + it("should return false if all child collections are moved to trash", async function () { + var collection1 = await createDataObject('collection'); + var collection2 = await createDataObject('collection', { parentID: collection1.id }); + var collection3 = await createDataObject('collection', { parentID: collection1.id }); + + assert.isTrue(collection1.hasChildCollections()); + collection2.deleted = true; + await collection2.saveTx(); + assert.isTrue(collection1.hasChildCollections()); + collection3.deleted = true; + await collection3.saveTx(); + assert.isFalse(collection1.hasChildCollections()); + }); + + it("should return true if child collection is in trash and includeTrashed is true", async function () { + var collection1 = await createDataObject('collection'); + var collection2 = await createDataObject('collection', { parentID: collection1.id }); + + assert.isTrue(collection1.hasChildCollections(true)); + collection2.deleted = true; + await collection2.saveTx(); + assert.isTrue(collection1.hasChildCollections(true)); + }); }) describe("#getChildCollections()", function () { diff --git a/test/tests/dataObjectTest.js b/test/tests/dataObjectTest.js index ac645be3bb..8b2c5a3a83 100644 --- a/test/tests/dataObjectTest.js +++ b/test/tests/dataObjectTest.js @@ -160,6 +160,35 @@ describe("Zotero.DataObject", function() { }); }) + + describe("#deleted", function () { + it("should set trash status", async function () { + for (let type of types) { + let plural = Zotero.DataObjectUtilities.getObjectTypePlural(type) + let pluralClass = Zotero[Zotero.Utilities.capitalize(plural)]; + + // Set to true + var obj = await createDataObject(type); + assert.isFalse(obj.deleted, type); + obj.deleted = true; + // Sanity check for itemsTest#trash() + if (type == 'item') { + assert.isTrue(obj._changedData.deleted, type); + } + await obj.saveTx(); + var id = obj.id; + await pluralClass.reload(id, false, true); + assert.isTrue(obj.deleted, type); + + // Set to false + obj.deleted = false; + await obj.saveTx(); + await pluralClass.reload(id, false, true); + assert.isFalse(obj.deleted, type); + } + }); + }); + describe("#loadPrimaryData()", function () { it("should load unloaded primary data if partially set", function* () { var objs = {}; @@ -574,5 +603,79 @@ describe("Zotero.DataObject", function() { assert.equal(item1.getField('dateModified'), dateModified); }) }) - }) + }); + + describe("#fromJSON()", function () { + it("should remove object from trash if 'deleted' property not provided", async function () { + for (let type of types) { + let obj = await createDataObject(type, { deleted: true }); + + assert.isTrue(obj.deleted, type); + + let json = obj.toJSON(); + delete json.deleted; + + obj.fromJSON(json); + await obj.saveTx(); + + assert.isFalse(obj.deleted, type); + } + }); + }); + + describe("#toJSON()", function () { + it("should output 'deleted' as true", function () { + for (let type of types) { + let obj = createUnsavedDataObject(type); + obj.deleted = true; + let json = obj.toJSON(); + assert.isTrue(json.deleted, type); + } + }); + + it("shouldn't include 'deleted' if not set in default mode", function () { + for (let type of types) { + let obj = createUnsavedDataObject(type); + let json = obj.toJSON(); + assert.notProperty(json, 'deleted', type); + } + }); + + describe("'patch' mode", function () { + it("should include changed 'deleted' field", async function () { + for (let type of types) { + let plural = Zotero.DataObjectUtilities.getObjectTypePlural(type) + let pluralClass = Zotero[Zotero.Utilities.capitalize(plural)]; + + // True to false + let obj = createUnsavedDataObject(type) + obj.deleted = true; + let id = await obj.saveTx(); + obj = await pluralClass.getAsync(id); + let patchBase = obj.toJSON(); + + obj.deleted = false; + let json = obj.toJSON({ + patchBase: patchBase + }) + assert.isUndefined(json.title, type); + assert.isFalse(json.deleted, type); + + // False to true + obj = createUnsavedDataObject(type); + obj.deleted = false; + id = await obj.saveTx(); + obj = await pluralClass.getAsync(id); + patchBase = obj.toJSON(); + + obj.deleted = true; + json = obj.toJSON({ + patchBase: patchBase + }) + assert.isUndefined(json.title, type); + assert.isTrue(json.deleted, type); + } + }); + }); + }); }) diff --git a/test/tests/itemTest.js b/test/tests/itemTest.js index a6d29c1883..6fb0d18ca6 100644 --- a/test/tests/itemTest.js +++ b/test/tests/itemTest.js @@ -321,28 +321,6 @@ describe("Zotero.Item", function () { }) }) - describe("#deleted", function () { - it("should be set to true after save", function* () { - var item = yield createDataObject('item'); - item.deleted = true; - // Sanity check for itemsTest#trash() - assert.isTrue(item._changed.deleted); - yield item.saveTx(); - assert.ok(item.deleted); - }) - - it("should be set to false after save", function* () { - var collection = yield createDataObject('collection'); - var item = createUnsavedDataObject('item'); - item.deleted = true; - yield item.saveTx(); - - item.deleted = false; - yield item.saveTx(); - assert.isFalse(item.deleted); - }) - }) - describe("#inPublications", function () { it("should add item to publications table", function* () { var item = yield createDataObject('item'); @@ -1500,20 +1478,6 @@ describe("Zotero.Item", function () { assert.isUndefined(json.numPages); }) - it("should output 'deleted' as 1", function* () { - var itemType = "book"; - var title = "Test"; - - var item = new Zotero.Item(itemType); - item.setField("title", title); - item.deleted = true; - var id = yield item.saveTx(); - item = Zotero.Items.get(id); - var json = item.toJSON(); - - assert.strictEqual(json.deleted, 1); - }) - it.skip("should output attachment fields from file", function* () { var file = getTestDataDirectory(); file.append('test.png'); @@ -1662,36 +1626,6 @@ describe("Zotero.Item", function () { assert.isUndefined(json.tags); }) - it("should include changed 'deleted' field", function* () { - // True to false - var item = new Zotero.Item('book'); - item.deleted = true; - var id = yield item.saveTx(); - item = yield Zotero.Items.getAsync(id); - var patchBase = item.toJSON(); - - item.deleted = false; - var json = item.toJSON({ - patchBase: patchBase - }) - assert.isUndefined(json.title); - assert.isFalse(json.deleted); - - // False to true - var item = new Zotero.Item('book'); - item.deleted = false; - var id = yield item.saveTx(); - item = yield Zotero.Items.getAsync(id); - var patchBase = item.toJSON(); - - item.deleted = true; - var json = item.toJSON({ - patchBase: patchBase - }) - assert.isUndefined(json.title); - assert.strictEqual(json.deleted, 1); - }) - it("should set 'parentItem' to false when cleared", function* () { var item = yield createDataObject('item'); var note = new Zotero.Item('note'); @@ -1810,19 +1744,6 @@ describe("Zotero.Item", function () { 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 }); diff --git a/test/tests/searchTest.js b/test/tests/searchTest.js index 3f34722dd4..3d550364e3 100644 --- a/test/tests/searchTest.js +++ b/test/tests/searchTest.js @@ -498,6 +498,19 @@ describe("Zotero.Search", function() { }); }); + describe("#deleted", function () { + it("should set trash status", async function () { + var search = await createDataObject('search'); + assert.isFalse(search.deleted); + search.deleted = true; + await search.saveTx(); + assert.isTrue(search.deleted); + search.deleted = false; + await search.saveTx(); + assert.isFalse(search.deleted); + }); + }); + describe("#toJSON()", function () { it("should output all data", function* () { let s = new Zotero.Search();