Repository: parse-community/Parse-SDK-JS
Pull request #1233 (2020-10-18 15:48:54)

Add a findAll method to ParseQuery
To get around the 100 objects maximum retrieval on the Parse Server side I used to do `Parse.Query().limit(9999).find()`.

And after looking at the code I found out about the `eachBatch` method which is really neat and I wonder if you'd consider adding a `findAll` method the the Parse.Query class based on it.

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1233?src=pr&el=h1) Report
> Merging [#1233](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1233?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/f2673a778a291fb345f846d0859d34e78e28c109?el=desc) will **decrease** coverage by `0.08%`.
> The diff coverage is `0.00%`.

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

```diff
@@            Coverage Diff             @@
##           master    #1233      +/-   ##
==========================================
- Coverage   92.38%   92.30%   -0.09%     
==========================================
  Files          54       54              
  Lines        5320     5325       +5     
  Branches     1201     1201              
==========================================
  Hits         4915     4915              
- Misses        405      410       +5     
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1233?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseQuery.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1233/diff?src=pr&el=tree#diff-c3JjL1BhcnNlUXVlcnkuanM=) | `95.29% <0.00%> (-0.71%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1233?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/1233?src=pr&el=footer). Last update [f2673a7...546848f](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1233?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

peril-parse-community[bot]: 
<!--
  0 failure: 
  0 warning: 
  
  1 markdown notices
  DangerID: danger-id-Peril;
-->



Danger run resulted in 1 fail and 1 markdown; to find out more, see the [checks page](https://github.com/parse-community/Parse-SDK-JS/runs/1276399283).
<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a>
</p>


Patch:
diff --git a/src/ParseQuery.js b/src/ParseQuery.js
index 4089082..e063e8d 100644
--- a/src/ParseQuery.js
+++ b/src/ParseQuery.js
@@ -673,6 +673,28 @@ class ParseQuery {
     });
   }
 
+  /**
+   * Retrieves a complete list of ParseObjects that satisfy this query.
+   * Using `eachBatch` under the hood to fetch all the valid objects.
+   *
+   * @param {Object} options Valid options are:<ul>
+   *   <li>batchSize: How many objects to yield in each batch (default: 100)
+   *   <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 {Promise} A promise that is resolved with the results when
+   * the query completes.
+   */
+  async findAll(options?: BatchOptions): Promise<Array<ParseObject>> {
+    let result: ParseObject[] = [];
+    await this.eachBatch((objects: ParseObject[]) => {
+      result = [...result, ...objects];
+    }, options);
+    return result;
+  }
+
   /**
    * Counts the number of objects that match this query.
    * Either options.success or options.error is called when the count


Test patch:
diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js
index f5be13e..0dd2038 100644
--- a/integration/test/ParseQueryTest.js
+++ b/integration/test/ParseQueryTest.js
@@ -1223,6 +1223,18 @@ describe('Parse Query', () => {
     });
   });
 
+  it('can return all objects with findAll', async () => {
+    const objs = [...Array(101)].map(() => new Parse.Object('Container'));
+
+    await Parse.Object.saveAll(objs);
+
+    const query = new Parse.Query('Container');
+
+    const result = await query.findAll();
+
+    assert.equal(result.length, 101);
+  });
+
   it('can include nested objects via array', (done) => {
     const child = new TestObject();
     const parent = new Parse.Object('Container');
diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js
index abe148f..a31a7bc 100644
--- a/src/__tests__/ParseQuery-test.js
+++ b/src/__tests__/ParseQuery-test.js
@@ -1638,6 +1638,110 @@ describe('ParseQuery', () => {
     });
   });
 
+  describe('return all objects with .findAll()', () => {
+    let findMock;
+    beforeEach(() => {
+      findMock = jest.fn();
+      findMock.mockReturnValueOnce(Promise.resolve({
+        results: [
+          { objectId: 'I55', size: 'medium', name: 'Product 55' },
+          { objectId: 'I89', size: 'small', name: 'Product 89' },
+        ]
+      }));
+      findMock.mockReturnValueOnce(Promise.resolve({
+        results: [
+          { objectId: 'I91', size: 'small', name: 'Product 91' },
+        ]
+      }));
+      CoreManager.setQueryController({
+        aggregate() {},
+        find: findMock,
+      });
+    });
+
+    it('passes query attributes through to the REST API', async () => {
+      const q = new ParseQuery('Item');
+      q.containedIn('size', ['small', 'medium']);
+      q.matchesKeyInQuery(
+        'name',
+        'productName',
+        new ParseQuery('Review').equalTo('stars', 5)
+      );
+      q.equalTo('valid', true);
+      q.select('size', 'name');
+      q.includeAll();
+      q.hint('_id_');
+
+      await q.findAll();
+      expect(findMock).toHaveBeenCalledTimes(1);
+      const [className, params, options] = findMock.mock.calls[0];
+      expect(className).toBe('Item')
+      expect(params).toEqual({
+        limit: 100,
+        order: 'objectId',
+        keys: 'size,name',
+        include: '*',
+        hint: '_id_',
+        where: {
+          size: {
+            $in: ['small', 'medium']
+          },
+          name: {
+            $select: {
+              key: 'productName',
+              query: {
+                className: 'Review',
+                where: {
+                  stars: 5
+                }
+              }
+            }
+          },
+          valid: true
+        }
+      });
+      expect(options.requestTask).toBeDefined();
+    });
+
+    it('passes options through to the REST API', async () => {
+      const batchOptions = {
+        useMasterKey: true,
+        sessionToken: '1234',
+        batchSize: 50,
+      };
+      const q = new ParseQuery('Item');
+      await q.findAll(batchOptions);
+      expect(findMock).toHaveBeenCalledTimes(1);
+      const [className, params, options] = findMock.mock.calls[0];
+      expect(className).toBe('Item');
+      expect(params).toEqual({
+        limit: 50,
+        order: 'objectId',
+        where: {},
+      });
+      expect(options.useMasterKey).toBe(true);
+      expect(options.sessionToken).toEqual('1234');
+    });
+
+    it('only makes one request when the results fit in one page', async () => {
+      const q = new ParseQuery('Item');
+      await q.findAll();
+      expect(findMock).toHaveBeenCalledTimes(1);
+    });
+
+    it('makes more requests when the results do not fit in one page', async () => {
+      const q = new ParseQuery('Item');
+      await q.findAll({ batchSize: 2 });
+      expect(findMock).toHaveBeenCalledTimes(2);
+    })
+
+    it('Returns all objects', async () => {
+      const q = new ParseQuery('Item');
+      const results = await q.findAll();
+      expect(results.map(obj => obj.attributes.size)).toEqual(['medium', 'small']);
+    });
+  });
+
   it('can iterate over results with each()', (done) => {
     CoreManager.setQueryController({
       aggregate() {},