Repository: parse-community/Parse-SDK-JS
Pull request #492 (2017-10-21 16:41:31)

Fix 477
Closes #477 

Fixes parse-community/parse-server#3999 

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

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

```diff
@@            Coverage Diff             @@
##           master     #492      +/-   ##
==========================================
+ Coverage   84.03%   84.04%   +0.01%     
==========================================
  Files          47       47              
  Lines        3795     3798       +3     
  Branches      868      869       +1     
==========================================
+ Hits         3189     3192       +3     
  Misses        606      606
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/492?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseQuery.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/492?src=pr&el=tree#diff-c3JjL1BhcnNlUXVlcnkuanM=) | `94.08% <100%> (+0.05%)` | :arrow_up: |

------

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


Patch:
diff --git a/src/ParseQuery.js b/src/ParseQuery.js
index 77fc9b1..796856c 100644
--- a/src/ParseQuery.js
+++ b/src/ParseQuery.js
@@ -67,16 +67,19 @@ function handleSelectResult(data: any, select: Array<string>){
 
       pathComponents.forEach((component, index, arr) => {
         // add keys if the expected data is missing
-        if (!obj[component]) {
-          obj[component] = (index == arr.length-1) ? undefined : {};
+        if (obj && !obj.hasOwnProperty(component)) {
+          obj[component] = undefined;
+        }
+        if (obj !== undefined) {
+          obj = obj[component];
         }
-        obj = obj[component];
 
         //add this path component to the server mask so we can fill it in later if needed
         if (index < arr.length-1) {
           if (!serverMask[component]) {
             serverMask[component] = {};
           }
+          serverMask = serverMask[component];
         }
       });
     }
@@ -100,8 +103,10 @@ function handleSelectResult(data: any, select: Array<string>){
         }
       }
       for (var key in mask) {
-        //traverse into objects as needed
-        copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+        if (dest[key] !== undefined && dest[key] !== null && src !== undefined && src !== null) {
+          //traverse into objects as needed
+          copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+        }
       }
     }
 


Test patch:
diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js
index 61d3b6c..d1d329b 100644
--- a/src/__tests__/ParseQuery-test.js
+++ b/src/__tests__/ParseQuery-test.js
@@ -1584,4 +1584,105 @@ describe('ParseQuery', () => {
     });
 
   });
+
+  it('selecting sub-objects does not inject objects when sub-object does not exist', (done) => {
+    jest.dontMock("../ParseObject");
+    jest.resetModules();
+    ParseObject = require('../ParseObject').default;
+    CoreManager = require('../CoreManager');
+    ParseQuery = require('../ParseQuery').default;
+
+    ParseObject.enableSingleInstance();
+    
+    var objectToReturn = { 
+      objectId: 'T01', 
+      name: 'Name', 
+      tbd: 'exists', 
+      className:"Thing", 
+      createdAt: '2017-01-10T10:00:00Z'
+    };
+
+    CoreManager.setQueryController({
+      find(className, params, options) {
+        return ParsePromise.as({
+          results: [objectToReturn]
+        });
+      }
+    });
+
+    var q = new ParseQuery("Thing");
+    q.select("other", "tbd", "subObject.key1")
+    var testObject;
+    return q.find().then((results) => {
+      testObject = results[0];
+      
+      expect(testObject.get("name")).toBe("Name");
+      expect(testObject.has("other")).toBe(false);
+      expect(testObject.has("subObject")).toBe(false);
+
+    }).then(() => {
+      done();
+    }, (error) => {
+      done.fail(error);
+    });
+  });
+
+  it('removes missing sub objects from the cached object when they are selected', (done) => {
+    jest.dontMock("../ParseObject");
+    jest.resetModules();
+    ParseObject = require('../ParseObject').default;
+    CoreManager = require('../CoreManager');
+    ParseQuery = require('../ParseQuery').default;
+
+    ParseObject.enableSingleInstance();
+    
+    var objectToReturn = { 
+      objectId: 'T01', 
+      name: 'Name', 
+      tbd: 'exists', 
+      className:"Thing", 
+      subObject1: {foo:"bar"},
+      subObject2: {foo:"bar"},
+      subObject3: {foo:"bar"},
+      subObject5: {subSubObject:{foo:"foo", bar:"bar"}},
+      createdAt: '2017-01-10T10:00:00Z'
+    };
+
+    CoreManager.setQueryController({
+      find(className, params, options) {
+        return ParsePromise.as({
+          results: [objectToReturn]
+        });
+      }
+    });
+
+    var q = new ParseQuery("Thing");
+    var testObject;
+    return q.find().then((results) => {
+      testObject = results[0];
+      
+      expect(testObject.has("subObject1")).toBe(true);
+      expect(testObject.has("subObject2")).toBe(true);
+      expect(testObject.has("subObject3")).toBe(true);
+      expect(testObject.has("subObject4")).toBe(false);
+
+      var q2 = new ParseQuery("Thing");
+      q.select("name","subObject1", "subObject2.foo", "subObject4.foo", "subObject5.subSubObject.foo");
+      objectToReturn = { objectId: 'T01', name:"Name", subObject4: {foo:"bar"}, subObject5: {subSubObject:{}}};
+      return q.find();
+    }).then((results)=>{
+      expect(testObject.has("subObject1")).toBe(false); //selected and not returned
+      expect(testObject.has("subObject2")).toBe(false); //selected and not returned
+      expect(testObject.has("subObject3")).toBe(true); //not selected, so should still be there
+      expect(testObject.has("subObject4")).toBe(true); //selected and just added
+      expect(testObject.has("subObject5")).toBe(true); 
+      expect(testObject.get("subObject5").subSubObject).toBeDefined();
+      expect(testObject.get("subObject5").subSubObject.bar).toBeDefined(); //not selected but a sibiling was, so should still be there
+    }).then(() => {
+      done();
+    }, (error) => {
+      done.fail(error);
+    });
+  });
+
 });