www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 9c52ebdf8b0911b722aedc32a428157db7cef35f
parent 8f38b01712d8c3c04e2889ca6a3f4f77d07c81d6
Author: Dan Stillman <dstillman@zotero.org>
Date:   Thu,  6 Oct 2016 01:14:37 -0400

Show saved searches under "Collection" search condition

With icons to identify collections and searches

Also:

- `savedSearch` search condition in general
- Clean up some search window code
- Reorganize search tests

Diffstat:
Mchrome/content/zotero/bindings/zoterosearch.xml | 105+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mchrome/content/zotero/xpcom/collectionTreeView.js | 1+
Mchrome/content/zotero/xpcom/data/collection.js | 3+++
Mchrome/content/zotero/xpcom/data/collections.js | 11++++++-----
Mchrome/content/zotero/xpcom/data/search.js | 30+++++++++++++++++++++++++-----
Mchrome/content/zotero/xpcom/data/searchConditions.js | 2+-
Mchrome/content/zotero/xpcom/data/searches.js | 18++++++++++++++++++
Mtest/tests/advancedSearchTest.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/tests/collectionsTest.js | 9+++++++++
Mtest/tests/searchTest.js | 199++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
10 files changed, 379 insertions(+), 139 deletions(-)

diff --git a/chrome/content/zotero/bindings/zoterosearch.xml b/chrome/content/zotero/bindings/zoterosearch.xml @@ -425,68 +425,63 @@ var rows = []; var libraryID = this.parent.search.libraryID; - var cols = Zotero.Collections.getByLibrary(libraryID, true); - for (var i in cols) { + + // Add collections + let cols = Zotero.Collections.getByLibrary(libraryID, true); + for (let col of cols) { // Indent subcollections var indent = ''; - if (cols[i].level) { - for (var j=1; j<cols[i].level; j++) { + if (col.level) { + for (let j = 1; j < col.level; j++) { indent += ' '; } indent += '- '; } - rows.push([indent + cols[i].name, 'C' + cols[i].key]); + rows.push({ + name: indent + col.name, + value: 'C' + col.key, + image: Zotero.Collection.prototype.treeViewImage + }); } - this.createValueMenu(rows); - break; - - case 'savedSearch': - var rows = []; - var libraryID = this.parent.search.libraryID; - var searches = Zotero.Searches.getAll(libraryID); - for (var i in searches) { - if (searches[i].id != this.parent.search.id) { - rows.push([searches[i].name, 'S' + searches[i].key]); + + // Add saved searches + let searches = Zotero.Searches.getByLibrary(libraryID); + for (let search of searches) { + if (search.id != this.parent.search.id) { + rows.push({ + name: search.name, + value: 'S' + search.key, + image: Zotero.Search.prototype.treeViewImage + }); } } this.createValueMenu(rows); break; case 'itemType': - var t = Zotero.ItemTypes.getTypes(); + var rows = Zotero.ItemTypes.getTypes().map(type => ({ + name: Zotero.ItemTypes.getLocalizedString(type.id), + value: type.name + })); // Sort by localized name - var types = []; - for (var i=0; i<t.length; i++) { - types.push({ - id: t[i].id, - name: t[i].name, - localized: Zotero.ItemTypes.getLocalizedString(t[i].id) - }); - } var collation = Zotero.getLocaleCollation(); - types.sort(function(a, b) { - return collation.compareString(1, a.localized, b.localized); - }); + rows.sort((a, b) => collation.compareString(1, a.name, b.name)); - for (var i in types) { - types[i][0] = types[i].localized; - types[i][1] = types[i].name; - delete types[i]['name']; - delete types[i]['id']; - } - this.createValueMenu(types); + this.createValueMenu(rows); break; case 'fileTypeID': - var types = Zotero.FileTypes.getTypes(); - for (var i in types) { - types[i][0] = Zotero.getString('fileTypes.' + types[i]['name']); - types[i][1] = types[i]['id']; - delete types[i]['name']; - delete types[i]['id']; - } - this.createValueMenu(types); + var rows = Zotero.FileTypes.getTypes().map(type => ({ + name: Zotero.getString('fileTypes.' + type.name), + value: type.id + })); + + // Sort by localized name + var collation = Zotero.getLocaleCollation(); + rows.sort((a, b) => collation.compareString(1, a.name, b.name)); + + this.createValueMenu(rows); break; default: @@ -522,7 +517,6 @@ // Drop-down menu if (conditionsMenu.value == 'collection' - || conditionsMenu.value == 'savedSearch' || conditionsMenu.value == 'itemType' || conditionsMenu.value == 'fileTypeID') { this.id('valuefield').hidden = true; @@ -557,19 +551,21 @@ </body> </method> <method name="createValueMenu"> - <parameter name="values"/> + <parameter name="rows"/> <body> <![CDATA[ while (this.id('valuemenu').hasChildNodes()){ this.id('valuemenu').removeChild(this.id('valuemenu').firstChild); } - if (values.length){ - for (var i in values){ - this.id('valuemenu').appendItem(values[i][0], values[i][1]); + for (let row of rows) { + let menuitem = this.id('valuemenu').appendItem(row.name, row.value); + if (row.image) { + menuitem.className = 'menuitem-iconic'; + menuitem.setAttribute('image', row.image); } - this.id('valuemenu').selectedIndex = 0; } + this.id('valuemenu').selectedIndex = 0; if (this.value) { @@ -601,7 +597,15 @@ break; } - this.id('conditionsmenu').value = condition.condition; + // Map certain conditions to other menu items + var uiCondition = condition.condition; + switch (condition.condition) { + case 'savedSearch': + uiCondition = 'collection'; + break; + } + + this.id('conditionsmenu').value = uiCondition; // Convert datetimes from UTC to localtime if ((condition['condition']=='accessDate' || @@ -664,8 +668,7 @@ // Handle special C1234 and S5678 form for // collections and searches - else if (this.id('conditionsmenu').value == 'collection' || this.id('conditionsmenu').value == 'savedSearch') - { + else if (condition == 'collection') { var letter = this.id('valuemenu').value.substr(0,1); if (letter=='C') { diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -766,6 +766,7 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col) case 'collection': case 'search': + // Keep in sync with Zotero.(Collection|Search).prototype.treeViewImage if (Zotero.isMac) { return "chrome://zotero-platform/content/treesource-" + collectionType + ".png"; } diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js @@ -91,6 +91,9 @@ Zotero.defineProperty(Zotero.Collection.prototype, 'treeViewID', { Zotero.defineProperty(Zotero.Collection.prototype, 'treeViewImage', { get: function () { + if (Zotero.isMac) { + return "chrome://zotero-platform/content/treesource-collection.png"; + } return "chrome://zotero/skin/treesource-collection" + Zotero.hiDPISuffix + ".png"; } }); diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js @@ -91,6 +91,7 @@ Zotero.Collections = function() { for (let id in this._objectCache) { let c = this._objectCache[id]; if (c.libraryID == libraryID && !c.parentKey) { + c.level = 0; children.push(c); } } @@ -112,11 +113,11 @@ Zotero.Collections = function() { var obj = children[i]; toReturn.push(obj); - var desc = obj.getDescendents(false, 'collection'); - for (var j in desc) { - var obj2 = this.get(desc[j].id); + var descendants = obj.getDescendents(false, 'collection'); + for (let d of descendants) { + var obj2 = this.get(d.id); if (!obj2) { - throw new Error('Collection ' + desc[j] + ' not found'); + throw new Error('Collection ' + d.id + ' not found'); } // TODO: This is a quick hack so that we can indent subcollections @@ -125,7 +126,7 @@ Zotero.Collections = function() { // of calculating that without either storing it in the DB or // changing the schema to Modified Preorder Tree Traversal, // and I don't know if we'll actually need it anywhere else. - obj2.level = desc[j].level; + obj2.level = d.level; toReturn.push(obj2); } diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js @@ -92,6 +92,20 @@ Zotero.defineProperty(Zotero.Search.prototype, '_canHaveParent', { value: false }); +Zotero.defineProperty(Zotero.Search.prototype, 'treeViewID', { + get: function () { + return "S" + this.id + } +}); + +Zotero.defineProperty(Zotero.Search.prototype, 'treeViewImage', { + get: function () { + if (Zotero.isMac) { + return "chrome://zotero-platform/content/treesource-search.png"; + } + return "chrome://zotero/skin/treesource-search" + Zotero.hiDPISuffix + ".png"; + } +}); Zotero.Search.prototype.loadFromRow = function (row) { var primaryFields = this._ObjectsClass.primaryFields; @@ -1184,9 +1198,10 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () { condSQL += "collectionID IN (" + q.join() + ")"; condSQLParams = condSQLParams.concat(p); } + // Saved search else { - // Check if there are any post-search filters - var hasFilter = search.hasPostSearchFilter(); + // Check if there are any post-search filters + var hasFilter = obj.hasPostSearchFilter(); // This is an ugly and inefficient way of doing a // subsearch, but it's necessary if there are any @@ -1197,13 +1212,18 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () { // or that this slows things down with large libraries // -- should probably use a temporary table instead if (hasFilter){ - let subids = yield search.search(); + let subids = yield obj.search(); condSQL += subids.join(); } // Otherwise just put the SQL in a subquery else { - condSQL += yield search.getSQL(); - let subpar = yield search.getSQLParams(); + condSQL += "itemID "; + if (condition.operator == 'isNot') { + condSQL += "NOT "; + } + condSQL += "IN ("; + condSQL += yield obj.getSQL(); + let subpar = yield obj.getSQLParams(); for (let k in subpar){ condSQLParams.push(subpar[k]); } diff --git a/chrome/content/zotero/xpcom/data/searchConditions.js b/chrome/content/zotero/xpcom/data/searchConditions.js @@ -239,7 +239,7 @@ Zotero.SearchConditions = new function(){ is: true, isNot: true }, - special: false + special: true }, { diff --git a/chrome/content/zotero/xpcom/data/searches.js b/chrome/content/zotero/xpcom/data/searches.js @@ -47,6 +47,24 @@ Zotero.Searches = function() { }); + this.getByLibrary = function (libraryID) { + var searches = []; + for (let id in this._objectCache) { + let s = this._objectCache[id]; + if (s.libraryID == libraryID) { + searches.push(s); + } + } + + // Do proper collation sort + var collation = Zotero.getLocaleCollation(); + searches.sort(function (a, b) { + return collation.compareString(1, a.name, b.name); + }); + return searches; + }; + + /** * Returns an array of Zotero.Search objects, ordered by name * diff --git a/test/tests/advancedSearchTest.js b/test/tests/advancedSearchTest.js @@ -4,6 +4,10 @@ describe("Advanced Search", function () { var win, zp; before(function* () { + yield resetDB({ + thisArg: this, + skipBundledFiles: true + }); win = yield loadZoteroPane(); zp = win.ZoteroPane; }); @@ -40,5 +44,141 @@ describe("Advanced Search", function () { assert.isNumber(index); searchWin.close(); + + item.eraseTx(); + }); + + describe("Conditions", function () { + var searchWin, searchBox, conditions; + + before(function* () { + var promise = waitForWindow('chrome://zotero/content/advancedSearch.xul'); + zp.openAdvancedSearchWindow(); + searchWin = yield promise; + searchBox = searchWin.document.getElementById('zotero-search-box'); + conditions = searchBox.id('conditions'); + }); + + after(function () { + searchWin.close(); + }); + + describe("Collection", function () { + it("should show collections and saved searches", function* () { + var col1 = yield createDataObject('collection', { name: "A" }); + var col2 = yield createDataObject('collection', { name: "C", parentID: col1.id }); + var col3 = yield createDataObject('collection', { name: "D", parentID: col2.id }); + var col4 = yield createDataObject('collection', { name: "B" }); + var search1 = yield createDataObject('search', { name: "A" }); + var search2 = yield createDataObject('search', { name: "B" }); + + // Add condition + var s = new Zotero.Search(); + s.addCondition('title', 'is', ''); + searchBox.search = s; + + var searchCondition = conditions.firstChild; + var conditionsMenu = searchCondition.id('conditionsmenu'); + var valueMenu = searchCondition.id('valuemenu'); + + assert.isTrue(valueMenu.hidden); + // Select 'Collection' condition + for (let i = 0; i < conditionsMenu.itemCount; i++) { + let menuitem = conditionsMenu.getItemAtIndex(i); + if (menuitem.value == 'collection') { + menuitem.click(); + break; + } + } + + assert.isFalse(valueMenu.hidden); + assert.equal(valueMenu.itemCount, 6); + var valueMenuItem = valueMenu.getItemAtIndex(1); + assert.equal(valueMenuItem.getAttribute('label'), "- " + col2.name); + assert.equal(valueMenuItem.getAttribute('value'), "C" + col2.key); + valueMenuItem = valueMenu.getItemAtIndex(2); + assert.equal(valueMenuItem.getAttribute('label'), " - " + col3.name); + assert.equal(valueMenuItem.getAttribute('value'), "C" + col3.key); + valueMenuItem = valueMenu.getItemAtIndex(4); + assert.equal(valueMenuItem.getAttribute('label'), search1.name); + assert.equal(valueMenuItem.getAttribute('value'), "S" + search1.key); + valueMenuItem = valueMenu.getItemAtIndex(5); + assert.equal(valueMenuItem.getAttribute('label'), search2.name); + assert.equal(valueMenuItem.getAttribute('value'), "S" + search2.key); + + yield Zotero.Collections.erase([col1.id, col2.id, col3.id, col4.id]); + yield Zotero.Searches.erase([search1.id, search2.id]); + }); + + it("should be selected for 'savedSearch' condition", function* () { + var search = yield createDataObject('search', { name: "A" }); + + var s = new Zotero.Search(); + s.addCondition('savedSearch', 'is', search.key); + searchBox.search = s; + + var searchCondition = conditions.firstChild; + var conditionsMenu = searchCondition.id('conditionsmenu'); + var valueMenu = searchCondition.id('valuemenu'); + + assert.equal(conditionsMenu.selectedItem.value, 'collection'); + assert.isFalse(valueMenu.hidden); + assert.equal(valueMenu.selectedItem.value, "S" + search.key); + + yield search.eraseTx(); + }); + + it("should set 'savedSearch' condition when a search is selected", function* () { + var collection = yield createDataObject('collection', { name: "A" }); + var search = yield createDataObject('search', { name: "B" }); + + var s = new Zotero.Search(); + s.addCondition('title', 'is', ''); + searchBox.search = s; + + var searchCondition = conditions.firstChild; + var conditionsMenu = searchCondition.id('conditionsmenu'); + var valueMenu = searchCondition.id('valuemenu'); + + // Select 'Collection' condition + for (let i = 0; i < conditionsMenu.itemCount; i++) { + let menuitem = conditionsMenu.getItemAtIndex(i); + if (menuitem.value == 'collection') { + menuitem.click(); + break; + } + } + for (let i = 0; i < valueMenu.itemCount; i++) { + let menuitem = valueMenu.getItemAtIndex(i); + if (menuitem.getAttribute('value') == "S" + search.key) { + menuitem.click(); + break; + } + } + + searchBox.updateSearch(); + var condition = searchBox.search.getConditions()[0]; + assert.equal(condition.condition, 'savedSearch'); + assert.equal(condition.value, search.key); + + yield collection.eraseTx(); + yield search.eraseTx(); + }); + }); + + describe("Saved Search", function () { + it("shouldn't appear", function* () { + var searchCondition = conditions.firstChild; + var conditionsMenu = searchCondition.id('conditionsmenu'); + + // Make sure "Saved Search" isn't present + for (let i = 0; i < conditionsMenu.itemCount; i++) { + let menuitem = conditionsMenu.getItemAtIndex(i); + if (menuitem.value == 'savedSearch') { + assert.fail(); + } + } + }); + }); }); }); diff --git a/test/tests/collectionsTest.js b/test/tests/collectionsTest.js @@ -38,6 +38,15 @@ describe("Zotero.Collections", function () { assert.isBelow(ids.indexOf(col7.id), ids.indexOf(col6.id), "F before G"); assert.isBelow(ids.indexOf(col6.id), ids.indexOf(col5.id), "G before D sibling E"); assert.isBelow(ids.indexOf(col5.id), ids.indexOf(col1.id), "E before A sibling C"); + + // 'level' property, which is a hack for indenting in the advanced search window + assert.equal(cols[0].level, 0); + assert.equal(cols[1].level, 1); + assert.equal(cols[2].level, 1); + assert.equal(cols[3].level, 2); + assert.equal(cols[4].level, 2); + assert.equal(cols[5].level, 1); + assert.equal(cols[6].level, 0); }) }) diff --git a/test/tests/searchTest.js b/test/tests/searchTest.js @@ -105,88 +105,133 @@ describe("Zotero.Search", function() { yield foobarItem.eraseTx(); }); - it("should find item in collection", function* () { - var col = yield createDataObject('collection'); - var item = yield createDataObject('item', { collections: [col.id] }); + describe("Conditions", function () { + describe("collection", function () { + it("should find item in collection", function* () { + var col = yield createDataObject('collection'); + var item = yield createDataObject('item', { collections: [col.id] }); + + var s = new Zotero.Search(); + s.libraryID = item.libraryID; + s.addCondition('collection', 'is', col.key); + var matches = yield s.search(); + assert.sameMembers(matches, [item.id]); + }); + + it("should find items not in collection", function* () { + var col = yield createDataObject('collection'); + var item = yield createDataObject('item', { collections: [col.id] }); + + var s = new Zotero.Search(); + s.libraryID = item.libraryID; + s.addCondition('collection', 'isNot', col.key); + var matches = yield s.search(); + assert.notInclude(matches, item.id); + }); + + it("shouldn't find item in collection with no items", function* () { + var col = yield createDataObject('collection'); + var item = yield createDataObject('item'); + + var s = new Zotero.Search(); + s.libraryID = item.libraryID; + s.addCondition('collection', 'is', col.key); + var matches = yield s.search(); + assert.lengthOf(matches, 0); + }); + + it("should find item in subcollection in recursive mode", function* () { + var col1 = yield createDataObject('collection'); + var col2 = yield createDataObject('collection', { parentID: col1.id }); + var item = yield createDataObject('item', { collections: [col2.id] }); + + var s = new Zotero.Search(); + s.libraryID = item.libraryID; + s.addCondition('collection', 'is', col1.key); + s.addCondition('recursive', 'true'); + var matches = yield s.search(); + assert.sameMembers(matches, [item.id]); + }); + }); - var s = new Zotero.Search(); - s.libraryID = item.libraryID; - s.addCondition('collection', 'is', col.key); - var matches = yield s.search(); - assert.sameMembers(matches, [item.id]); - }); - - it("shouldn't find item in collection with no items", function* () { - var col = yield createDataObject('collection'); - var item = yield createDataObject('item'); + describe("fileTypeID", function () { + it("should search by attachment file type", function* () { + let s = new Zotero.Search(); + s.addCondition('fileTypeID', 'is', Zotero.FileTypes.getID('webpage')); + let matches = yield s.search(); + assert.sameMembers(matches, [fooItem.id, foobarItem.id]); + }); + }); - var s = new Zotero.Search(); - s.libraryID = item.libraryID; - s.addCondition('collection', 'is', col.key); - var matches = yield s.search(); - assert.lengthOf(matches, 0); - }); + describe("fulltextWord", function () { + it("should return matches with full-text conditions", function* () { + let s = new Zotero.Search(); + s.addCondition('fulltextWord', 'contains', 'foo'); + let matches = yield s.search(); + assert.lengthOf(matches, 2); + assert.sameMembers(matches, [fooItem.id, foobarItem.id]); + }); - it("should find item in subcollection in recursive mode", function* () { - var col1 = yield createDataObject('collection'); - var col2 = yield createDataObject('collection', { parentID: col1.id }); - var item = yield createDataObject('item', { collections: [col2.id] }); - - var s = new Zotero.Search(); - s.libraryID = item.libraryID; - s.addCondition('collection', 'is', col1.key); - s.addCondition('recursive', 'true'); - var matches = yield s.search(); - assert.sameMembers(matches, [item.id]); - }); + it("should not return non-matches with full-text conditions", function* () { + let s = new Zotero.Search(); + s.addCondition('fulltextWord', 'contains', 'baz'); + let matches = yield s.search(); + assert.lengthOf(matches, 0); + }); - it("should return matches with full-text conditions", function* () { - let s = new Zotero.Search(); - s.addCondition('fulltextWord', 'contains', 'foo'); - let matches = yield s.search(); - assert.lengthOf(matches, 2); - assert.sameMembers(matches, [fooItem.id, foobarItem.id]); - }); - - it("should not return non-matches with full-text conditions", function* () { - let s = new Zotero.Search(); - s.addCondition('fulltextWord', 'contains', 'baz'); - let matches = yield s.search(); - assert.lengthOf(matches, 0); - }); - - it("should return matches for full-text conditions in ALL mode", function* () { - let s = new Zotero.Search(); - s.addCondition('joinMode', 'all'); - s.addCondition('fulltextWord', 'contains', 'foo'); - s.addCondition('fulltextWord', 'contains', 'bar'); - let matches = yield s.search(); - assert.deepEqual(matches, [foobarItem.id]); - }); - - it("should not return non-matches for full-text conditions in ALL mode", function* () { - let s = new Zotero.Search(); - s.addCondition('joinMode', 'all'); - s.addCondition('fulltextWord', 'contains', 'mjktkiuewf'); - s.addCondition('fulltextWord', 'contains', 'zijajkvudk'); - let matches = yield s.search(); - assert.lengthOf(matches, 0); - }); - - it("should return a match that satisfies only one of two full-text condition in ANY mode", function* () { - let s = new Zotero.Search(); - s.addCondition('joinMode', 'any'); - s.addCondition('fulltextWord', 'contains', 'bar'); - s.addCondition('fulltextWord', 'contains', 'baz'); - let matches = yield s.search(); - assert.deepEqual(matches, [foobarItem.id]); - }); + it("should return matches for full-text conditions in ALL mode", function* () { + let s = new Zotero.Search(); + s.addCondition('joinMode', 'all'); + s.addCondition('fulltextWord', 'contains', 'foo'); + s.addCondition('fulltextWord', 'contains', 'bar'); + let matches = yield s.search(); + assert.deepEqual(matches, [foobarItem.id]); + }); - it("should search by attachment file type", function* () { - let s = new Zotero.Search(); - s.addCondition('fileTypeID', 'is', Zotero.FileTypes.getID('webpage')); - let matches = yield s.search(); - assert.sameMembers(matches, [fooItem.id, foobarItem.id]); + it("should not return non-matches for full-text conditions in ALL mode", function* () { + let s = new Zotero.Search(); + s.addCondition('joinMode', 'all'); + s.addCondition('fulltextWord', 'contains', 'mjktkiuewf'); + s.addCondition('fulltextWord', 'contains', 'zijajkvudk'); + let matches = yield s.search(); + assert.lengthOf(matches, 0); + }); + + it("should return a match that satisfies only one of two full-text condition in ANY mode", function* () { + let s = new Zotero.Search(); + s.addCondition('joinMode', 'any'); + s.addCondition('fulltextWord', 'contains', 'bar'); + s.addCondition('fulltextWord', 'contains', 'baz'); + let matches = yield s.search(); + assert.deepEqual(matches, [foobarItem.id]); + }); + }); + + describe("savedSearch", function () { + it("should return items in the saved search", function* () { + var search = yield createDataObject('search'); + var itemTitle = search.getConditions()[0].value; + var item = yield createDataObject('item', { title: itemTitle }) + + var s = new Zotero.Search; + s.libraryID = Zotero.Libraries.userLibraryID; + s.addCondition('savedSearch', 'is', search.key); + var matches = yield s.search(); + assert.deepEqual(matches, [item.id]); + }); + + it("should return items not in the saved search for isNot operator", function* () { + var search = yield createDataObject('search'); + var itemTitle = search.getConditions()[0].value; + var item = yield createDataObject('item', { title: itemTitle }) + + var s = new Zotero.Search; + s.libraryID = Zotero.Libraries.userLibraryID; + s.addCondition('savedSearch', 'isNot', search.key); + var matches = yield s.search(); + assert.notInclude(matches, item.id); + }); + }); }); });