Repository: baianat/vee-validate
Pull request #1999 (2019-04-08 11:58:52)

Add verbose error when path isn't within object
🔎 __Overview__

Recently, I had an error
`Uncaught TypeError: Cannot use 'in' operator to search for 'someKeyHere' in`
While I debug things, I think it's better to have a full path as part of stack trace.

WDYT ?

Ex (Feat):
> This PR {adds/fixes/improves} the {feature/bug/something}.

Ex (Locale):
> This PR changes the {locale} messages style because {reason}

🤓 __Code snippets/examples (if applicable)__

```js
// some code
```

✔ __Issues affected__

list of issues formatted like this:

> closes #{issue id}


LIQRGV: @logaretm, I've add some test as well to verify my code works.
Can you review this ?
codecov[bot]: # [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1999?src=pr&el=h1) Report
> Merging [#1999](https://codecov.io/gh/baianat/vee-validate/pull/1999?src=pr&el=desc) into [master](https://codecov.io/gh/baianat/vee-validate/commit/7f482bd8ab0b607bbd2df1e9794f20b323faef94?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/baianat/vee-validate/pull/1999/graphs/tree.svg?width=650&token=XFzlvHKFP5&height=150&src=pr)](https://codecov.io/gh/baianat/vee-validate/pull/1999?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1999      +/-   ##
==========================================
+ Coverage   95.94%   95.95%   +0.01%     
==========================================
  Files          62       62              
  Lines        2341     2350       +9     
  Branches      729      735       +6     
==========================================
+ Hits         2246     2255       +9     
  Misses         86       86              
  Partials        9        9
```


| [Impacted Files](https://codecov.io/gh/baianat/vee-validate/pull/1999?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/utils/index.js](https://codecov.io/gh/baianat/vee-validate/pull/1999/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2luZGV4Lmpz) | `97.75% <100%> (+0.07%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1999?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/baianat/vee-validate/pull/1999?src=pr&el=footer). Last update [7f482bd...ea9ab08](https://codecov.io/gh/baianat/vee-validate/pull/1999?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/guide/rules.md b/docs/guide/rules.md
index 4df60142..89212151 100644
--- a/docs/guide/rules.md
+++ b/docs/guide/rules.md
@@ -171,6 +171,7 @@ The field under validation must have the same value as the confirmation field.
 <span v-show="errors.has('password')" class="help is-danger">{{ errors.first('password') }}</span>
 
 <input v-validate="'required|confirmed:password'" name="password_confirmation" type="password" :class="{'is-danger': errors.has('password_confirmation')}" placeholder="Password, Again" data-vv-as="password">
+<span v-show="errors.has('password_confirmation')" class="help is-danger">{{ errors.first('password_confirmation') }}</span>
 ```
 
 ::: tip
diff --git a/src/utils/index.js b/src/utils/index.js
index a6118b6e..943027df 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -148,15 +148,29 @@ export const getPath = (path: string, target: ?Object, def: any = undefined) =>
  */
 export const hasPath = (path: string, target: Object) => {
   let obj = target;
-  return path.split('.').every(prop => {
+  let previousPath = null;
+  let isNullOrNonObject = false;
+  const isValidPath = path.split('.').reduce((reducer, prop) => {
+    if (obj == null || typeof obj != 'object') {
+      isNullOrNonObject = true;
+      return reducer && false;
+    }
+
     if (prop in obj) {
       obj = obj[prop];
+      previousPath = previousPath === null ? prop : previousPath + '.' + prop;
 
-      return true;
+      return reducer && true;
     }
 
-    return false;
-  });
+    return reducer && false;
+  }, true);
+
+  if (isNullOrNonObject) {
+    throw new Error(previousPath + ' is not an object');
+  }
+
+  return isValidPath;
 };
 
 /**


Test patch:
diff --git a/tests/unit/utils.js b/tests/unit/utils.js
index 872e1a15..05eed7e1 100644
--- a/tests/unit/utils.js
+++ b/tests/unit/utils.js
@@ -202,13 +202,25 @@ test('checks if a value path with exists', () => {
   const some = {
     value: {
       path: undefined,
-      val: 1
+      nullValue: null,
+      val: 1,
+      someString: ''
     }
   };
 
   expect(utils.hasPath('value.val', some)).toBe(true); // exists.
   expect(utils.hasPath('value.path', some)).toBe(true); // undefined but exists.
-  expect(utils.hasPath('value.not', some)).toBe(false); // does not.
+  expect(utils.hasPath('value.nullValue', some)).toBe(true); // null but exists.
+  expect(utils.hasPath('value.notExist', some)).toBe(false); // does not.
+  expect(() => {
+    utils.hasPath('value.val.notExistAttr', some);
+  }).toThrow('value.val is not an object'); // throw error because not object.
+  expect(() => {
+    utils.hasPath('value.someString.notExistAttr', some);
+  }).toThrow('value.someString is not an object'); // throw error because not object.
+  expect(() => {
+    utils.hasPath('value.nullValue.notExistAttr', some);
+  }).toThrow('value.nullValue is not an object'); // throw error because not object.
 });
 
 test('gets the value path with a fallback value', () => {