don't allow deletion of docs without rev

if we have no rev, bulk deletion is not possible, so we should
not try to offer it.

Signed-off-by: Michelle Phung <michellep@apache.org>
diff --git a/app/addons/documents/index-results/index-results.components.react.jsx b/app/addons/documents/index-results/index-results.components.react.jsx
index bbcd196..92cdfdc 100644
--- a/app/addons/documents/index-results/index-results.components.react.jsx
+++ b/app/addons/documents/index-results/index-results.components.react.jsx
@@ -86,8 +86,8 @@
     },
 
     maybeGetCheckboxCell: function (i) {
-      if (!this.props.docId) {
-        return null;
+      if (!this.props.data.isDeletable) {
+        return <td className="tableview-checkbox-cell" key={"tableview-checkbox-cell-" + i}></td>;
       }
 
       return (
@@ -123,6 +123,7 @@
         return (
           <TableRow
             key={"tableview-row-component-" + i}
+            isListDeletable={this.props.isListDeletable}
             index={i}
             data={el}
             docId={el.id}
@@ -258,13 +259,13 @@
       var mainView = isTableView ? this.getTableStyleView(loadLines) : this.getDocumentStyleView(loadLines);
       return (
         <div className="document-result-screen">
-          <BulkActionComponent
+          {this.props.isListDeletable ? <BulkActionComponent
             removeItem={this.props.removeItem}
             isChecked={this.props.allDocumentsSelected}
             hasSelectedItem={this.props.hasSelectedItem}
             selectAll={this.selectAllDocs}
             toggleSelect={this.toggleSelectAll}
-            title="Select all docs that can be..." />
+            title="Select all docs that can be..." /> : null}
           {mainView}
         </div>
       );
@@ -347,7 +348,7 @@
           canSelectAll={this.state.canSelectAll}
           isSelected={this.isSelected}
           isEditable={this.state.isEditable}
-          isListDeletable={this.state.results.hasEditableAndDeletableDoc}
+          isListDeletable={this.state.results.hasDeletableDoc}
           docChecked={this.docChecked}
           isLoading={this.state.isLoading}
           results={this.state.results}
diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js
index 18d2f06..5a4766e 100644
--- a/app/addons/documents/index-results/stores.js
+++ b/app/addons/documents/index-results/stores.js
@@ -104,7 +104,7 @@
         return false;
       }
 
-      return doc.isDeletable();
+      return doc.isBulkDeletable();
     },
 
     getCollection: function () {
@@ -179,29 +179,30 @@
 
     },
 
+    filterOutGeneratedMangoDocs: function (doc) {
+      if (doc.get && typeof doc.get === 'function') {
+        return doc.get('language') !== 'query';
+      }
+
+      return doc.language !== 'query';
+    },
+
     getResults: function () {
-      var hasEditableAndDeletableDoc;
+      var hasDeletableDoc;
       var res;
       var collection;
 
-
-      function filterOutGeneratedMangoDocs (doc) {
-        if (doc.get && typeof doc.get === 'function') {
-          return doc.get('language') !== 'query';
-        }
-
-        return doc.language !== 'query';
-      }
+      var filteredCollection = this._collection.filter(this.filterOutGeneratedMangoDocs);
 
       // Table sytle view
       if (this.getIsTableView()) {
-        collection = this._collection.toJSON().filter(filterOutGeneratedMangoDocs);
+        collection = this._collection.toJSON().filter(this.filterOutGeneratedMangoDocs);
         return this.getTableViewData(collection);
       }
 
       // JSON style views
       res = this._collection
-        .filter(filterOutGeneratedMangoDocs)
+        .filter(this.filterOutGeneratedMangoDocs)
         .map(function (doc, i) {
           if (doc.get('def') || this.isGhostDoc(doc)) {
             return this.getMangoDoc(doc, i);
@@ -217,10 +218,10 @@
           };
         }, this);
 
-      hasEditableAndDeletableDoc = this.getHasEditableAndDeletableDoc(res);
+      hasDeletableDoc = this.getHasDeletableDoc(res);
 
       return {
-        hasEditableAndDeletableDoc: hasEditableAndDeletableDoc,
+        hasDeletableDoc: hasDeletableDoc,
         results: res
       };
     },
@@ -259,7 +260,7 @@
       return data;
     },
 
-    getTableViewData: function (data, hasEditableAndDeletableDoc) {
+    getTableViewData: function (data) {
       var res;
       var schema;
       var database;
@@ -268,42 +269,36 @@
       schema = this.getPseudoSchema(data);
       database = this.getDatabase().safeID();
 
-      res = data.map(function (doc) {
-        var safeId = app.utils.getSafeIdForDoc(doc._id);
-        var url;
+      res = this._collection
+        .filter(this.filterOutGeneratedMangoDocs)
+        .map(function (doc) {
 
-        if (safeId) {
-          url = FauxtonAPI.urls('document', 'app', database, safeId);
-        }
-
-        return {
-          content: doc,
-          id: safeId,
-          header: '',
-          keylabel: '',
-          url: url,
-          isDeletable: !!safeId,
-          isEditable: !!safeId
-        };
-      });
-
-      hasEditableAndDeletableDoc = this.getHasEditableAndDeletableDoc(res);
+          return {
+            content: doc.toJSON(),
+            id: this.getDocId(doc),
+            header: '',
+            keylabel: '',
+            url: this.getDocId(doc) ? doc.url('app') : null,
+            isDeletable: this.isDeletable(doc),
+            isEditable: this.isEditable(doc)
+          };
+        }, this);
 
       return {
-        hasEditableAndDeletableDoc: hasEditableAndDeletableDoc,
+        hasDeletableDoc: this.getHasDeletableDoc(res),
         schema: schema,
         results: res
       };
     },
 
-    getHasEditableAndDeletableDoc: function (data) {
+    getHasDeletableDoc: function (data) {
       var found = false;
       var length = data.length;
       var i;
-
       // use a for loop here as we can end it once we found the first id
       for (i = 0; i < length; i++) {
-        if (data[i].id) {
+
+        if (data[i].isDeletable) {
           found = true;
           break;
         }
@@ -343,6 +338,9 @@
     selectAllDocuments: function () {
       this.clearSelectedItems();
       this._collection.each(function (doc) {
+        if (!doc.isBulkDeletable()) {
+          return;
+        }
         this.selectDoc(doc.id);
       }, this);
     },
@@ -356,11 +354,17 @@
     },
 
     areAllDocumentsSelected: function () {
+      var filtered;
+
       if (this._collection.length === 0) {
         return false;
       }
 
-      return Object.keys(this._selectedItems).length === this._collection.length;
+      filtered = this._collection.filter(function (doc) {
+        return doc.isBulkDeletable();
+      });
+
+      return Object.keys(this._selectedItems).length === filtered.length;
     },
 
     getSelectedItemsLength: function () {
diff --git a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
index ee4811c..0589c7a 100644
--- a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
+++ b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
@@ -16,9 +16,11 @@
   'addons/documents/index-results/stores',
   'addons/documents/resources',
   'addons/databases/resources',
+
+  'addons/documents/tests/document-test-helper',
   'testUtils',
   "react"
-], function (FauxtonAPI, Views, IndexResultsActions, Stores, Documents, Databases, utils, React) {
+], function (FauxtonAPI, Views, IndexResultsActions, Stores, Documents, Databases, documentTestHelper, utils, React) {
   FauxtonAPI.router = new FauxtonAPI.Router([]);
 
   var assert = utils.assert;
@@ -82,17 +84,13 @@
         store.reset();
       });
 
-      function createDocColumn (docs) {
-        docs = docs.map(function (doc) {
-          return Documents.Doc.prototype.parse(doc);
-        });
+      var createDocColumn = documentTestHelper.createDocColumn;
+      var createMangoIndexDocColumn = documentTestHelper.createMangoIndexDocColumn;
 
-        return new Documents.AllDocs(docs, opts);
-      }
 
-      it('does not render checkboxes for elements with no id in a table', function () {
+      it('does not render checkboxes for elements with just the special index (Mango Index List)', function () {
         IndexResultsActions.sendMessageNewResultList({
-          collection: createDocColumn([{foo: 'testId1'}, {bar: 'testId1'}])
+          collection: createMangoIndexDocColumn([{foo: 'testId1', type: 'special'}])
         });
 
         store.enableTableView();
@@ -109,9 +107,82 @@
         assert.ok($el.find('.tableview-header-el-checkbox').length === 0);
       });
 
-      it('renders checkboxes for elements with id in a table', function () {
+      it('renders checkboxes for elements with more than just the the special index (Mango Index List)', function () {
         IndexResultsActions.sendMessageNewResultList({
-          collection: createDocColumn([{id: '1', foo: 'testId1'}, {bar: 'testId1'}])
+          collection: createMangoIndexDocColumn([{
+            ddoc: null,
+            name: 'biene',
+            type: 'json',
+            def: {fields: [{_id: 'desc'}]}
+          },
+          {
+            ddoc: null,
+            name: 'biene',
+            type: 'special',
+            def: {fields: [{_id: 'desc'}]}
+          }])
+        });
+
+        store.enableTableView();
+
+        IndexResultsActions.resultsListReset();
+
+        instance = TestUtils.renderIntoDocument(
+          <Views.List />,
+          container
+        );
+
+        var $el = $(instance.getDOMNode());
+
+        assert.ok($el.find('.tableview-header-el-checkbox').length > 0);
+      });
+
+      it('does not render checkboxes for elements with no id in a table (usual docs)', function () {
+        IndexResultsActions.sendMessageNewResultList({
+          collection: createDocColumn([{
+            ddoc: null,
+            name: 'biene',
+            type: 'special',
+            def: {fields: [{_id: 'desc'}]}
+          }])
+        });
+
+        store.enableTableView();
+
+        IndexResultsActions.resultsListReset();
+
+        instance = TestUtils.renderIntoDocument(
+          <Views.List />,
+          container
+        );
+
+        var $el = $(instance.getDOMNode());
+
+        assert.ok($el.find('.tableview-header-el-checkbox').length === 0);
+      });
+
+      it('does not render checkboxes for elements with no rev in a table (usual docs)', function () {
+        IndexResultsActions.sendMessageNewResultList({
+          collection: createDocColumn([{id: '1', foo: 'testId1'}, {id: '1', bar: 'testId1'}])
+        });
+
+        store.enableTableView();
+
+        IndexResultsActions.resultsListReset();
+
+        instance = TestUtils.renderIntoDocument(
+          <Views.List />,
+          container
+        );
+
+        var $el = $(instance.getDOMNode());
+
+        assert.ok($el.find('.tableview-header-el-checkbox').length === 0);
+      });
+
+      it('renders checkboxes for elements with an id and rev in a table (usual docs)', function () {
+        IndexResultsActions.sendMessageNewResultList({
+          collection: createDocColumn([{id: '1', foo: 'testId1', rev: 'foo'}, {bar: 'testId1', rev: 'foo'}])
         });
 
         store.enableTableView();
@@ -128,9 +199,9 @@
         assert.ok($el.find('.tableview-checkbox-cell').length > 0);
       });
 
-      it('renders checkboxes for elements with an id in a json view', function () {
+      it('renders checkboxes for elements with an id and rev in a json view (usual docs)', function () {
         IndexResultsActions.sendMessageNewResultList({
-          collection: createDocColumn([{id: '1', foo: 'testId1'}, {bar: 'testId1'}])
+          collection: createDocColumn([{id: '1', emma: 'testId1', rev: 'foo'}, {bar: 'testId1'}])
         });
 
         IndexResultsActions.resultsListReset();
@@ -143,13 +214,12 @@
         );
 
         var $el = $(instance.getDOMNode());
-
         assert.ok($el.find('.js-row-select').length > 0);
       });
 
-      it('does not render checkboxes for elements with no id in a json view', function () {
+      it('does not render checkboxes for elements with that are not deletable in a json view (usual docs)', function () {
         IndexResultsActions.sendMessageNewResultList({
-          collection: createDocColumn([{foo: 'testId1'}, {bar: 'testId1'}])
+          collection: createDocColumn([{foo: 'testId1', rev: 'foo'}, {bar: 'testId1'}])
         });
 
         IndexResultsActions.resultsListReset();
diff --git a/app/addons/documents/index-results/tests/index-results.storesSpec.js b/app/addons/documents/index-results/tests/index-results.storesSpec.js
index 304cf87..dec5d50 100644
--- a/app/addons/documents/index-results/tests/index-results.storesSpec.js
+++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js
@@ -15,13 +15,18 @@
   'addons/documents/index-results/stores',
   'addons/documents/index-results/actiontypes',
   'addons/documents/shared-resources',
+  'addons/documents/tests/document-test-helper',
+
   'testUtils'
-], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) {
+], function (FauxtonAPI, Stores, ActionTypes, Documents, documentTestHelper, testUtils) {
   var assert = testUtils.assert;
   var dispatchToken;
   var store;
   var opts;
 
+  var createDocColumn = documentTestHelper.createDocColumn;
+  var createMangoIndexDocColumn = documentTestHelper.createMangoIndexDocColumn;
+
   describe('Index Results Store', function () {
     beforeEach(function () {
       store = new Stores.IndexResultsStore();
@@ -120,20 +125,21 @@
       assert.deepEqual(doclist, res);
     });
 
-    it('finds out if we have at least one editable/deleteable doc which needs an id', function () {
+    it('finds out if we have at least one editable/deleteable doc', function () {
       var doclist = [
-        {id: 'testId2', foo: 'one'},
+        {id: 'testId2', foo: 'one', isDeletable: true},
         {id: 'testId3', foo: 'two'}
       ];
 
-      assert.ok(store.getHasEditableAndDeletableDoc(doclist));
+      assert.ok(store.getHasDeletableDoc(doclist));
 
       doclist = [
         {foo: 'one'},
         {foo: 'two'}
       ];
 
-      assert.notOk(store.getHasEditableAndDeletableDoc(doclist));
+      assert.notOk(store.getHasDeletableDoc(doclist));
+
     });
 
     it('if the collection is empty, no docs should be selected', function () {
@@ -142,6 +148,17 @@
       assert.notOk(store.areAllDocumentsSelected());
     });
 
+    it('special mango docs are not selectable', function () {
+      store._collection = createMangoIndexDocColumn([
+        {ddoc: 'testId1', type: 'special', def: {fields: [{_id: 'desc'}]}},
+        {ddoc: 'testId2', blubb: 'ba', type: 'json', def: {fields: [{_id: 'desc'}]}}
+      ]);
+
+      store.selectAllDocuments();
+
+      assert.ok(store.areAllDocumentsSelected());
+    });
+
   });
 
   describe('canSelectAll', function () {
@@ -236,18 +253,25 @@
   describe('#selectAllDocuments', function () {
 
     it('selects all documents', function () {
-      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts);
+      store._collection = createDocColumn([{_id: 'testId1', _rev: '1', 'value': 'one'}]);
 
       store.selectAllDocuments();
       assert.ok(store.getSelectedItems().testId1);
     });
 
+    it('does not select all documents if rev is missing', function () {
+      store._collection = createDocColumn([{_id: 'testId1', 'value': 'one'}]);
+
+      store.selectAllDocuments();
+      assert.equal(store.getSelectedItemsLength(), 0);
+    });
+
   });
 
   describe('toggleSelectAllDocuments', function () {
 
     it('deselects all documents', function () {
-      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts);
+      store._collection = new Documents.AllDocs([{_id: 'testId1', _rev: '1', 'value': 'one'}], opts);
 
       store.selectAllDocuments();
       assert.ok(store.getSelectedItems().testId1);
@@ -257,7 +281,7 @@
 
     it('deselects all documents', function () {
       store.reset();
-      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts);
+      store._collection = new Documents.AllDocs([{_id: 'testId1', _rev: '1', 'value': 'one'}], opts);
 
       assert.equal(Object.keys(store.getSelectedItems()).length, 0);
       store.toggleSelectAllDocuments();
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index c6cbb29..e8d291c 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -94,6 +94,10 @@
       return this.get('type') !== 'special';
     },
 
+    isBulkDeletable: function () {
+      return this.isDeletable();
+    },
+
     isFromView: function () {
       return false;
     },
diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js
index 7a635d9..570cef6 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -57,6 +57,10 @@
       return app.utils.getDocTypeFromId(this.id);
     },
 
+    isBulkDeletable: function () {
+      return !!this.id && !!this.get('_rev');
+    },
+
     isDeletable: function () {
       return !!this.id;
     },
diff --git a/app/addons/documents/tests/document-test-helper.js b/app/addons/documents/tests/document-test-helper.js
new file mode 100644
index 0000000..c6ecce3
--- /dev/null
+++ b/app/addons/documents/tests/document-test-helper.js
@@ -0,0 +1,45 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+define([
+  'api',
+  'addons/documents/resources'
+], function (FauxtonAPI, Documents) {
+
+  var opts = {
+    params: {},
+    database: {
+      safeID: function () { return '1';}
+    }
+  };
+
+  function createDocColumn (docs) {
+    docs = docs.map(function (doc) {
+      return Documents.Doc.prototype.parse(doc);
+    });
+
+    return new Documents.AllDocs(docs, opts);
+  }
+
+  function createMangoIndexDocColumn (docs) {
+    docs = docs.map(function (doc) {
+      return Documents.MangoIndex.prototype.parse(doc);
+    });
+
+    return new Documents.MangoIndexCollection(docs, opts);
+  }
+
+  return {
+    createDocColumn: createDocColumn,
+    createMangoIndexDocColumn: createMangoIndexDocColumn
+  };
+
+});