Repository: parse-community/Parse-SDK-JS
Pull request #538 (2017-12-26 21:48:20)

Update README.md
Added note for React Native applications regarding the new function for setting the AsyncStorage

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538?src=pr&el=h1) Report
> Merging [#538](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/ee33c48e4a19f9264fd4301cb1dd3250dc7550a7?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538/graphs/tree.svg?token=8nCENM0QHj&src=pr&width=650&height=150)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #538   +/-   ##
=======================================
  Coverage   84.59%   84.59%           
=======================================
  Files          48       48           
  Lines        3952     3952           
  Branches      897      897           
=======================================
  Hits         3343     3343           
  Misses        609      609
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538?src=pr&el=footer). Last update [ee33c48...3715339](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/538?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index d8e8fc3..58ae038 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,10 @@ For React Native applications, include `'parse/react-native'`:
 ```js
 // In a React Native application
 var Parse = require('parse/react-native');
+
+// On React Native >= 0.50 and Parse >= 1.11.0, set the Async
+var AsyncStorage = require('react-native').AsyncStorage;
+Parse.setAsyncStorage(AsyncStorage);
 ```
 
 ## License
diff --git a/integration/package.json b/integration/package.json
index 5927ccb..68b1072 100644
--- a/integration/package.json
+++ b/integration/package.json
@@ -3,7 +3,7 @@
   "dependencies": {
     "express": "^4.13.4",
     "mocha": "^2.4.5",
-    "parse-server": "^2.7.0"
+    "parse-server": "^2.7.1"
   },
   "scripts": {
     "test": "mocha --reporter dot -t 5000"
diff --git a/src/CoreManager.js b/src/CoreManager.js
index 997f9cf..0d4a66b 100644
--- a/src/CoreManager.js
+++ b/src/CoreManager.js
@@ -78,6 +78,7 @@ type RESTController = {
   ajax: (method: string, url: string, data: any, headers?: any) => ParsePromise;
 };
 type SchemaController = {
+  purge: (className: string) => ParsePromise;
   get: (className: string, options: RequestOptions) => ParsePromise;
   delete: (className: string, options: RequestOptions) => ParsePromise;
   create: (className: string, params: any, options: RequestOptions) => ParsePromise;
@@ -295,7 +296,7 @@ module.exports = {
   },
 
   setSchemaController(controller: SchemaController) {
-    requireMethods('SchemaController', ['get', 'create', 'update', 'delete', 'send'], controller);
+    requireMethods('SchemaController', ['get', 'create', 'update', 'delete', 'send', 'purge'], controller);
     config['SchemaController'] = controller;
   },
 
diff --git a/src/ParseError.js b/src/ParseError.js
index e03499e..88d069e 100644
--- a/src/ParseError.js
+++ b/src/ParseError.js
@@ -20,7 +20,7 @@ class ParseError {
     this.code = code;
     this.message = message;
   }
-  
+
   toString() {
     return 'ParseError: ' + this.code + ' ' + this.message;
   }
@@ -466,6 +466,14 @@ ParseError.INVALID_LINKED_SESSION = 251;
  */
 ParseError.UNSUPPORTED_SERVICE = 252;
 
+/**
+ * Error code indicating an invalid operation occured on schema
+ * @property INVALID_SCHEMA_OPERATION
+ * @static
+ * @final
+ */
+ParseError.INVALID_SCHEMA_OPERATION = 255;
+
 /**
  * Error code indicating that there were multiple errors. Aggregate errors
  * have an "errors" property, which is an array of error objects with more
diff --git a/src/ParsePolygon.js b/src/ParsePolygon.js
index 8219b5c..0666f61 100644
--- a/src/ParsePolygon.js
+++ b/src/ParsePolygon.js
@@ -17,7 +17,7 @@ import ParseGeoPoint from './ParseGeoPoint';
  *   new Polygon([[0,0],[0,1],[1,1],[1,0]])
  *   new Polygon([GeoPoint, GeoPoint, GeoPoint])
  *   </pre>
- * 
+ *
  * <p>Represents a coordinates that may be associated
  * with a key in a ParseObject or used as a reference point for geo queries.
  * This allows proximity-based queries on the key.</p>
@@ -56,7 +56,7 @@ class ParsePolygon {
   }
 
   /**
-   * Returns a JSON representation of the GeoPoint, suitable for Parse.
+   * Returns a JSON representation of the Polygon, suitable for Parse.
    * @return {Object}
    */
   toJSON(): { __type: string; coordinates: Array;} {
@@ -89,9 +89,9 @@ class ParsePolygon {
   }
 
   /**
-   * 
-   * @param {Parse.GeoPoint} point 
-   * @returns {Boolean} wether the points is contained into the polygon
+   *
+   * @param {Parse.GeoPoint} point
+   * @returns {Boolean} Returns if the point is contained in the polygon
    */
   containsPoint(point: ParseGeoPoint): boolean {
     let minX = this._coordinates[0][0];
diff --git a/src/ParseQuery.js b/src/ParseQuery.js
index 7bea22e..cfcc6fd 100644
--- a/src/ParseQuery.js
+++ b/src/ParseQuery.js
@@ -46,6 +46,24 @@ function quote(s: string) {
   return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E';
 }
 
+/**
+ * Extracts the class name from queries. If not all queries have the same
+ * class name an error will be thrown.
+ */
+function _getClassNameFromQueries(queries: Array<ParseQuery>): string {
+  var className = null;
+  queries.forEach((q) => {
+    if (!className) {
+      className = q.className;
+    }
+
+    if (className !== q.className) {
+      throw new Error('All queries must be for the same class.');
+    }
+  });
+  return className;
+}
+
 /*
  * Handles pre-populating the result data of a query with select fields,
  * making sure that the data object contains keys for all objects that have
@@ -229,6 +247,21 @@ class ParseQuery {
     return this;
   }
 
+  /**
+   * Adds constraint that all of the passed in queries match.
+   * @method _andQuery
+   * @param {Array} queries
+   * @return {Parse.Query} Returns the query, so you can chain this call.
+   */
+  _andQuery(queries: Array<ParseQuery>): ParseQuery {
+    var queryJSON = queries.map((q) => {
+      return q.toJSON().where;
+    });
+
+    this._where.$and = queryJSON;
+    return this;
+  }
+
   /**
    * Helper for condition queries
    */
@@ -954,6 +987,40 @@ class ParseQuery {
     return this._addCondition(key, '$regex', quote(value));
   }
 
+   /**
+  * Adds a constraint for finding string values that contain a provided
+  * string. This may be slow for large datasets. Requires Parse-Server > 2.5.0
+  *
+  * In order to sort you must use select and ascending ($score is required)
+  *  <pre>
+  *   query.fullText('term');
+  *   query.ascending('$score');
+  *   query.select('$score');
+  *  </pre>
+  *
+  * To retrieve the weight / rank
+  *  <pre>
+  *   object->get('score');
+  *  </pre>
+  *
+  * @param {String} key The key that the string to match is stored in.
+  * @param {String} value The string to search
+  * @return {Parse.Query} Returns the query, so you can chain this call.
+  */
+ fullText(key: string, value: string): ParseQuery {
+   if (!key) {
+     throw new Error('A key is required.');
+   }
+   if (!value) {
+     throw new Error('A search term is required');
+   }
+   if (typeof value !== 'string') {
+     throw new Error('The value being searched for must be a string.');
+   }
+
+   return this._addCondition(key, '$text', { $search: { $term: value } });
+ }
+
   /**
    * Adds a constraint for finding string values that start with a provided
    * string.  This query will use the backend index, so it will be fast even
@@ -1254,21 +1321,30 @@ class ParseQuery {
    * @return {Parse.Query} The query that is the OR of the passed in queries.
    */
   static or(...queries: Array<ParseQuery>): ParseQuery {
-    var className = null;
-    queries.forEach((q) => {
-      if (!className) {
-        className = q.className;
-      }
-
-      if (className !== q.className) {
-        throw new Error('All queries must be for the same class.');
-      }
-    });
-
+    var className = _getClassNameFromQueries(queries);
     var query = new ParseQuery(className);
     query._orQuery(queries);
     return query;
   }
+
+  /**
+   * Constructs a Parse.Query that is the AND of the passed in queries.  For
+   * example:
+   * <pre>var compoundQuery = Parse.Query.and(query1, query2, query3);</pre>
+   *
+   * will create a compoundQuery that is an and of the query1, query2, and
+   * query3.
+   * @method and
+   * @param {...Parse.Query} var_args The list of queries to AND.
+   * @static
+   * @return {Parse.Query} The query that is the AND of the passed in queries.
+   */
+  static and(...queries: Array<ParseQuery>): ParseQuery {
+    var className = _getClassNameFromQueries(queries);
+    var query = new ParseQuery(className);
+    query._andQuery(queries);
+    return query;
+  }
 }
 
 var DefaultController = {
diff --git a/src/ParseSchema.js b/src/ParseSchema.js
index 4eb8199..4577616 100644
--- a/src/ParseSchema.js
+++ b/src/ParseSchema.js
@@ -14,7 +14,7 @@ import ParsePromise from './ParsePromise';
 
 import type { RequestOptions, FullOptions } from './RESTController';
 
-const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Array', 'Object', 'Pointer', 'Relation'];
+const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Polygon', 'Array', 'Object', 'Pointer', 'Relation'];
 
 /**
  * A Parse.Schema object is for handling schema data from Parse.
@@ -181,6 +181,7 @@ class ParseSchema {
 
   /**
    * Removing a Schema from Parse
+   * Can only be used on Schema without objects
    *
    * @param {Object} options A Backbone-style options object.
    * Valid options are:<ul>
@@ -207,6 +208,34 @@ class ParseSchema {
       })._thenRunCallbacks(options);
   }
 
+  /**
+   * Removes all objects from a Schema (class) in Parse.
+   * EXERCISE CAUTION, running this will delete all objects for this schema and cannot be reversed
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  purge(options: FullOptions) {
+    this.assertClassName();
+
+    const controller = CoreManager.getSchemaController();
+
+    return controller.purge(this.className)
+      .then((response) => {
+        return response;
+      })._thenRunCallbacks(options);
+  }
+
   /**
    * Assert if ClassName has been filled
    * @private
@@ -319,6 +348,16 @@ class ParseSchema {
     return this.addField(name, 'GeoPoint');
   }
 
+  /**
+   * Adding Polygon Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addPolygon(name: string) {
+    return this.addField(name, 'Polygon');
+  }
+
   /**
    * Adding Array Field
    *
@@ -437,6 +476,16 @@ const DefaultController = {
 
   delete(className: string, options: RequestOptions): ParsePromise {
     return this.send(className, 'DELETE', {}, options);
+  },
+
+  purge(className: string): ParsePromise {
+    const RESTController = CoreManager.getRESTController();
+    return RESTController.request(
+      'DELETE',
+      `purge/${className}`,
+      {},
+      { useMasterKey: true }
+    );
   }
 };
 


Test patch:
diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js
index 41dc45a..a25a6c1 100644
--- a/integration/test/ParseQueryTest.js
+++ b/integration/test/ParseQueryTest.js
@@ -578,7 +578,7 @@ describe('Parse Query', () => {
       assert.equal(results[2].get('string'), 'd');
       assert.equal(results[3].get('number'), 1);
       assert.equal(results[3].get('string'), 'b');
-      
+
       let query = new Parse.Query(TestObject);
       query.equalTo('doubleDescending', true);
       query.descending('number, string');
@@ -608,7 +608,7 @@ describe('Parse Query', () => {
       assert.equal(results[2].get('string'), 'd');
       assert.equal(results[3].get('number'), 1);
       assert.equal(results[3].get('string'), 'b');
-      
+
       let query = new Parse.Query(TestObject);
       query.equalTo('doubleDescending', true);
       query.descending('number', 'string');
@@ -623,7 +623,7 @@ describe('Parse Query', () => {
       assert.equal(results[2].get('string'), 'd');
       assert.equal(results[3].get('number'), 1);
       assert.equal(results[3].get('string'), 'b');
-      
+
       done();
     });
   });
@@ -760,7 +760,7 @@ describe('Parse Query', () => {
       assert.equal(results.length, 2);
       assert.equal(results[0].id, objects[0].id);
       assert.equal(results[1].id, objects[1].id);
-      
+
       let query = new Parse.Query('TestObject');
       query.equalTo('timed2', true);
       query.greaterThan('createdAt', objects[2].createdAt);
@@ -1210,7 +1210,7 @@ describe('Parse Query', () => {
     }).then((results) => {
       assert.equal(results.length, 1);
       assert.equal(results[0].get('name'), 'Bob');
-      
+
       let query = new Parse.Query(Restaurant);
       query.greaterThan('rating', 4);
       let mainQuery = new Parse.Query(Person);
@@ -1310,6 +1310,60 @@ describe('Parse Query', () => {
     });
   });
 
+  it('can build AND queries', (done) => {
+    let objects = [];
+    for (let i = 0; i < 10; i++) {
+      let obj = new Parse.Object('BoxedNumber');
+      obj.set({ x: i, and: true });
+      objects.push(obj);
+    }
+    Parse.Object.saveAll(objects).then(() => {
+      let q1 = new Parse.Query('BoxedNumber');
+      q1.equalTo('and', true);
+      q1.greaterThan('x', 2);
+      let q2 = new Parse.Query('BoxedNumber');
+      q2.equalTo('and', true);
+      q2.lessThan('x', 5);
+      let andQuery = Parse.Query.and(q1, q2);
+      return andQuery.find();
+    }).then((results) => {
+      assert.equal(results.length, 2);
+      results.forEach((number) => {
+        assert(number.get('x') > 2 && number.get('x') < 5);
+      });
+      done();
+    }).fail(e => console.log(e));
+  });
+
+  it('can build complex AND queries', (done) => {
+    let objects = [];
+    for (let i = 0; i < 10; i++) {
+      let child = new Parse.Object('Child');
+      child.set('x', i);
+      child.set('and', true);
+      let parent = new Parse.Object('Parent');
+      parent.set('child', child);
+      parent.set('and', true);
+      parent.set('y', i);
+      objects.push(parent);
+    }
+    Parse.Object.saveAll(objects).then(() => {
+      let subQuery = new Parse.Query('Child');
+      subQuery.equalTo('x', 4);
+      subQuery.equalTo('and', true);
+      let q1 = new Parse.Query('Parent');
+      q1.matchesQuery('child', subQuery);
+      let q2 = new Parse.Query('Parent');
+      q2.equalTo('and', true);
+      q2.equalTo('y', 4);
+      let andQuery = new Parse.Query.and(q1, q2);
+      return andQuery.find();
+    }).then((results) => {
+      assert.equal(results.length, 1);
+      done();
+    }).fail(e => console.log(e));
+  });
+
   it('can iterate over results with each', (done) => {
     let items = [];
     for (let i = 0; i < 50; i++) {
@@ -1426,4 +1480,61 @@ describe('Parse Query', () => {
       done();
     });
   });
+
+  it('full text search', (done) => {
+    const subjects = [
+      'coffee',
+      'Coffee Shopping',
+      'Baking a cake',
+      'baking',
+      'Café Con Leche',
+      'Сырники',
+      'coffee and cream',
+      'Cafe con Leche',
+    ];
+    const objects = [];
+    for (const i in subjects) {
+      const obj = new TestObject({ subject: subjects[i] });
+      objects.push(obj);
+    }
+    Parse.Object.saveAll(objects).then(() => {
+      const q = new Parse.Query(TestObject);
+      q.fullText('subject', 'coffee');
+      return q.find();
+    }).then((results) => {
+      assert.equal(results.length, 3);
+      done();
+    });
+  });
+
+  it('full text search sort', (done) => {
+    const subjects = [
+      'coffee',
+      'Coffee Shopping',
+      'Baking a cake',
+      'baking',
+      'Café Con Leche',
+      'Сырники',
+      'coffee and cream',
+      'Cafe con Leche',
+    ];
+    const objects = [];
+    for (const i in subjects) {
+      const obj = new TestObject({ comment: subjects[i] });
+      objects.push(obj);
+    }
+    Parse.Object.saveAll(objects).then(() => {
+      const q = new Parse.Query(TestObject);
+      q.fullText('comment', 'coffee');
+      q.ascending('$score');
+      q.select('$score');
+      return q.find();
+    }).then((results) => {
+      assert.equal(results.length, 3);
+      assert.equal(results[0].get('score'), 1);
+      assert.equal(results[1].get('score'), 0.75);
+      assert.equal(results[2].get('score'), 0.75);
+      done();
+    });
+  });
 });
diff --git a/integration/test/ParseSchemaTest.js b/integration/test/ParseSchemaTest.js
index 91dfbff..cd50331 100644
--- a/integration/test/ParseSchemaTest.js
+++ b/integration/test/ParseSchemaTest.js
@@ -47,6 +47,7 @@ describe('Schema', () => {
       .addDate('dateField')
       .addFile('fileField')
       .addGeoPoint('geoPointField')
+      .addPolygon('polygonField')
       .addArray('arrayField')
       .addObject('objectField')
       .addPointer('pointerField', '_User')
@@ -62,6 +63,7 @@ describe('Schema', () => {
       assert.equal(result.fields.dateField.type, 'Date');
       assert.equal(result.fields.fileField.type, 'File');
       assert.equal(result.fields.geoPointField.type, 'GeoPoint');
+      assert.equal(result.fields.polygonField.type, 'Polygon');
       assert.equal(result.fields.arrayField.type, 'Array');
       assert.equal(result.fields.objectField.type, 'Object');
       assert.equal(result.fields.pointerField.type, 'Pointer');
@@ -132,6 +134,32 @@ describe('Schema', () => {
     });
   });
 
+  it('purge', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    const obj = new Parse.Object('SchemaTest');
+    obj.save().then(() => {
+        return testSchema.delete().then(() => {
+          // Should never reach here
+          assert.equal(true, false);
+        }).catch((error) => {
+          assert.equal(error.code, Parse.Error.INVALID_SCHEMA_OPERATION);
+          assert.equal(error.message, 'Class SchemaTest is not empty, contains 1 objects, cannot drop schema.');
+          return Parse.Promise.as();
+        });
+    }).then(() => {
+      return testSchema.purge();
+    }).then(() => {
+      const query = new Parse.Query('SchemaTest');
+      return query.count();
+    }).then((count) => {
+      assert.equal(count, 0);
+      // Delete only works on empty schema, extra check
+      return testSchema.delete();
+    }).then(() => {
+      done();
+    });
+  });
+
   it('save index', (done) => {
     const testSchema = new Parse.Schema('SchemaTest');
     const index = {
diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js
index 259fb13..30775c4 100644
--- a/src/__tests__/CoreManager-test.js
+++ b/src/__tests__/CoreManager-test.js
@@ -328,7 +328,8 @@ describe('CoreManager', () => {
       get: function() {},
       create: function() {},
       update: function() {},
-      delete: function() {}
+      delete: function() {},
+      purge: function() {},
     })).not.toThrow();
   });
 
@@ -338,7 +339,8 @@ describe('CoreManager', () => {
       get: function() {},
       create: function() {},
       update: function() {},
-      delete: function() {}
+      delete: function() {},
+      purge: function() {},
     };
 
     CoreManager.setSchemaController(controller);
diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js
index 78c01e5..0bc2ed9 100644
--- a/src/__tests__/ParseQuery-test.js
+++ b/src/__tests__/ParseQuery-test.js
@@ -883,6 +883,40 @@ describe('ParseQuery', () => {
     });
   });
 
+  it('can combine queries with an AND clause', () => {
+    var q = new ParseQuery('Item');
+    var q2 = new ParseQuery('Purchase');
+    expect(ParseQuery.and.bind(null, q, q2)).toThrow(
+      'All queries must be for the same class.'
+    );
+
+    q2 = new ParseQuery('Item');
+    q.equalTo('size', 'medium');
+    q2.equalTo('size', 'large');
+
+    var mediumOrLarge = ParseQuery.and(q, q2);
+    expect(mediumOrLarge.toJSON()).toEqual({
+      where: {
+        $and: [
+          { size: 'medium' },
+          { size: 'large' }
+        ]
+      }
+    });
+
+    // It removes limits, skips, etc
+    q.limit(10);
+    mediumOrLarge = ParseQuery.and(q, q2);
+    expect(mediumOrLarge.toJSON()).toEqual({
+      where: {
+        $and: [
+          { size: 'medium' },
+          { size: 'large' }
+        ]
+      }
+    });
+  });
+
   it('can get the first object of a query', (done) => {
     CoreManager.setQueryController({
       aggregate() {},
@@ -1864,4 +1898,59 @@ describe('ParseQuery', () => {
     });
   });
 
+  it('full text search', () => {
+    const query = new ParseQuery('Item');
+    query.fullText('size', 'small');
+
+    expect(query.toJSON()).toEqual({
+      where: {
+        size: {
+          $text: {
+            $search: {
+              $term: "small"
+            }
+          }
+        }
+      }
+    });
+  });
+
+  it('full text search sort', () => {
+    const query = new ParseQuery('Item');
+    query.fullText('size', 'medium');
+    query.ascending('$score');
+    query.select('$score');
+
+    expect(query.toJSON()).toEqual({
+      where: {
+        size: {
+          $text: {
+            $search: {
+              $term: "medium",
+            }
+          }
+        }
+      },
+      keys : "$score",
+      order : "$score"
+    });
+  });
+
+  it('full text search key required', (done) => {
+    const query = new ParseQuery('Item');
+    expect(() => query.fullText()).toThrow('A key is required.');
+    done();
+  });
+
+  it('full text search value required', (done) => {
+      const query = new ParseQuery('Item');
+      expect(() => query.fullText('key')).toThrow('A search term is required');
+      done();
+  });
+
+  it('full text search value must be string', (done) => {
+    const query = new ParseQuery('Item');
+    expect(() => query.fullText('key', [])).toThrow('The value being searched for must be a string.');
+    done();
+  });
 });
diff --git a/src/__tests__/ParseSchema-test.js b/src/__tests__/ParseSchema-test.js
index c99311c..cdc0046 100644
--- a/src/__tests__/ParseSchema-test.js
+++ b/src/__tests__/ParseSchema-test.js
@@ -13,15 +13,6 @@ var ParseSchema = require('../ParseSchema').default;
 var ParsePromise = require('../ParsePromise').default;
 var CoreManager = require('../CoreManager');
 
-function generateSaveMock(prefix) {
-  return function(name, payload) {
-    return ParsePromise.as({
-      name: name,
-      url: prefix + name
-    });
-  };
-}
-
 var defaultController = CoreManager.getSchemaController();
 
 describe('ParseSchema', () => {
@@ -56,6 +47,7 @@ describe('ParseSchema', () => {
       .addDate('dateField')
       .addFile('fileField')
       .addGeoPoint('geoPointField')
+      .addPolygon('polygonField')
       .addArray('arrayField')
       .addObject('objectField')
       .addPointer('pointerField', '_User')
@@ -68,6 +60,7 @@ describe('ParseSchema', () => {
     expect(schema._fields.dateField.type, 'Date');
     expect(schema._fields.fileField.type, 'File');
     expect(schema._fields.geoPointField.type, 'GeoPoint');
+    expect(schema._fields.polygonField.type, 'Polygon');
     expect(schema._fields.arrayField.type, 'Array');
     expect(schema._fields.objectField.type, 'Object');
     expect(schema._fields.pointerField.type, 'Pointer');
@@ -171,19 +164,13 @@ describe('ParseSchema', () => {
     done();
   });
 
-  // CoreManager.setSchemaController({
-  //   send() {},
-  //   get() {},
-  //   create() {},
-  //   update() {},
-  //   delete() {},
-  // });
   it('can save schema', (done) => {
     CoreManager.setSchemaController({
       send() {},
       get() {},
       update() {},
       delete() {},
+      purge() {},
       create(className, params, options) {
         expect(className).toBe('SchemaTest');
         expect(params).toEqual({
@@ -211,6 +198,7 @@ describe('ParseSchema', () => {
       get() {},
       create() {},
       delete() {},
+      purge() {},
       update(className, params, options) {
         expect(className).toBe('SchemaTest');
         expect(params).toEqual({
@@ -238,6 +226,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       get() {},
+      purge() {},
       delete(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({});
@@ -252,12 +241,33 @@ describe('ParseSchema', () => {
     });
   });
 
+  it('can purge schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      get() {},
+      delete() {},
+      purge(className) {
+        expect(className).toBe('SchemaTest');
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.purge().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
   it('can get schema', (done) => {
     CoreManager.setSchemaController({
       send() {},
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({});
@@ -278,6 +288,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({ sessionToken: 1234 });
@@ -298,6 +309,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({});
@@ -322,6 +334,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('');
         expect(options).toEqual({});
@@ -343,6 +356,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('');
         expect(options).toEqual({ sessionToken: 1234 });
@@ -364,6 +378,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('');
         expect(options).toEqual({});
@@ -388,11 +403,9 @@ describe('SchemaController', () => {
   beforeEach(() => {
     CoreManager.setSchemaController(defaultController);
     var request = function(method, path, data, options) {
-      var name = path.substr(path.indexOf('/') + 1);
       return ParsePromise.as([]);
     };
     var ajax = function(method, path, data, headers) {
-      var name = path.substr(path.indexOf('/') + 1);
       return ParsePromise.as([]);
     };
     CoreManager.setRESTController({ request: request, ajax: ajax });
@@ -429,4 +442,12 @@ describe('SchemaController', () => {
       done();
     });
   });
+
+  it('purge schema', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.purge().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
 });