Repository: baianat/vee-validate
Pull request #857 (2017-09-27 20:07:37)

Refactore more scope tests to use isNullOrUndefined(scope)
After allowing 0 scope, I found more places in the code that I neglected.


Patch:
diff --git a/src/core/errorBag.js b/src/core/errorBag.js
index f2a5564f..df6df6e4 100644
--- a/src/core/errorBag.js
+++ b/src/core/errorBag.js
@@ -17,19 +17,19 @@ export default class ErrorBag {
         field: arguments[0],
         msg: arguments[1],
         rule: arguments[2],
-        scope: arguments[3] || null
+        scope: !isNullOrUndefined(arguments[3]) ? arguments[3] : null
       };
     }
 
-    error.scope = error.scope || null;
+    error.scope = !isNullOrUndefined(error.scope) ? error.scope : null;
     this.items.push(error);
   }
 
   /**
    * Updates a field error with the new field scope.
    *
-   * @param {String} id 
-   * @param {Object} error 
+   * @param {String} id
+   * @param {Object} error
    */
   update (id, error) {
     const item = find(this.items, i => i.id === id);
@@ -50,7 +50,7 @@ export default class ErrorBag {
      * @return {Array} errors Array of all error messages.
      */
   all (scope) {
-    if (! scope) {
+    if (isNullOrUndefined(scope)) {
       return this.items.map(e => e.msg);
     }
 
@@ -63,7 +63,7 @@ export default class ErrorBag {
      * @return {boolean} result True if there was at least one error, false otherwise.
      */
   any (scope) {
-    if (! scope) {
+    if (isNullOrUndefined(scope)) {
       return !! this.items.length;
     }
 
@@ -76,7 +76,7 @@ export default class ErrorBag {
      * @param {String} scope The Scope name, optional.
      */
   clear (scope) {
-    if (! scope) {
+    if (isNullOrUndefined(scope)) {
       scope = null;
     }
 
@@ -113,7 +113,7 @@ export default class ErrorBag {
     }
 
     field = !isNullOrUndefined(field) ? String(field) : field;
-    if (! scope) {
+    if (isNullOrUndefined(scope)) {
       return this.items.filter(e => e.field === field).map(e => (map ? e.msg : e));
     }
 
@@ -132,7 +132,7 @@ export default class ErrorBag {
   /**
    * Finds and fetches the first error message for the specified field id.
    *
-   * @param {String} id 
+   * @param {String} id
    */
   firstById (id) {
     const error = find(this.items, i => i.id === id);
@@ -220,7 +220,7 @@ export default class ErrorBag {
 
   /**
    * Removes errors by matching against the id.
-   * @param {String} id 
+   * @param {String} id
    */
   removeById (id) {
     for (let i = 0; i < this.items.length; ++i) {
@@ -245,7 +245,7 @@ export default class ErrorBag {
         return e.id === id;
       }
 
-      if (scope) {
+      if (!isNullOrUndefined(scope)) {
         return e.field === field && e.scope === scope;
       }
 
diff --git a/src/core/field.js b/src/core/field.js
index 3f46d52e..f6d03fbd 100644
--- a/src/core/field.js
+++ b/src/core/field.js
@@ -94,7 +94,7 @@ export default class Field {
   }
 
   /**
-   * If the field rejects false as a valid value for the required rule. 
+   * If the field rejects false as a valid value for the required rule.
    */
   get rejectsFalse () {
     if (this.isVue || this.isHeadless) {
@@ -137,10 +137,11 @@ export default class Field {
     this.initial = options.initial || this.initial || false;
 
     // update errors scope if the field scope was changed.
-    if (this.updated && options.scope && options.scope !== this.scope && isCallable(this.validator.update)) {
+    if (this.updated && !isNullOrUndefined(options.scope) && options.scope !== this.scope && isCallable(this.validator.update)) {
       this.validator.update(this.id, { scope: options.scope });
     }
-    this.scope = options.scope || this.scope || null;
+    this.scope = !isNullOrUndefined(options.scope) ? options.scope
+      : !isNullOrUndefined(this.scope) ? this.scope : null;
     this.name = (!isNullOrUndefined(options.name) ? String(options.name) : options.name) || this.name || null;
     this.rules = options.rules !== undefined ? normalizeRules(options.rules) : this.rules;
     this.model = options.model || this.model;
@@ -194,7 +195,7 @@ export default class Field {
 
   /**
    * Sets the flags and their negated counterparts, and updates the classes and re-adds action listeners.
-   * @param {Object} flags 
+   * @param {Object} flags
    */
   setFlags (flags) {
     const negated = {
diff --git a/src/core/generator.js b/src/core/generator.js
index 5a751eb6..c8a6c42b 100644
--- a/src/core/generator.js
+++ b/src/core/generator.js
@@ -125,7 +125,7 @@ export default class Generator {
       scope = binding.value.scope;
     }
 
-    if (vnode.child && !scope) {
+    if (vnode.child && isNullOrUndefined(scope)) {
       scope = vnode.child.$attrs && vnode.child.$attrs['data-vv-scope'];
     }
 
diff --git a/src/core/utils.js b/src/core/utils.js
index 42481492..0a4ac50d 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -7,7 +7,7 @@ export const getDataAttribute = (el, name) => el.getAttribute(`data-vv-${name}`)
 
 /**
  * Checks if the value is either null or undefined.
- * @param {*} value 
+ * @param {*} value
  */
 export const isNullOrUndefined = (value) => {
   return value === null || value === undefined;
@@ -62,8 +62,8 @@ export const createFlags = () => ({
 /**
  * Shallow object comparison.
  *
- * @param {*} lhs 
- * @param {*} rhs 
+ * @param {*} lhs
+ * @param {*} rhs
  * @return {Boolean}
  */
 export const isEqual = (lhs, rhs) => {
@@ -100,11 +100,11 @@ export const isEqual = (lhs, rhs) => {
  */
 export const getScope = (el) => {
   let scope = getDataAttribute(el, 'scope');
-  if (! scope && el.form) {
+  if (isNullOrUndefined(scope) && el.form) {
     scope = getDataAttribute(el.form, 'scope');
   }
 
-  return scope || null;
+  return !isNullOrUndefined(scope) ? scope : null;
 };
 
 /**
diff --git a/src/core/validator.js b/src/core/validator.js
index 6fe6e3de..c329e71a 100644
--- a/src/core/validator.js
+++ b/src/core/validator.js
@@ -181,7 +181,7 @@ export default class Validator {
   /**
    * Adds a locale object to the dictionary.
    * @deprecated
-   * @param {Object} locale 
+   * @param {Object} locale
    */
   static addLocale (locale) {
     if (! locale.name) {
@@ -197,7 +197,7 @@ export default class Validator {
   /**
    * Adds a locale object to the dictionary.
    * @deprecated
-   * @param {Object} locale 
+   * @param {Object} locale
    */
   addLocale (locale) {
     Validator.addLocale(locale);
@@ -215,7 +215,7 @@ export default class Validator {
 
   /**
    * Adds and sets the current locale for the validator.
-   * 
+   *
    * @param {String} lang
    * @param {Object} dictionary
    */
@@ -296,9 +296,9 @@ export default class Validator {
     this.errors.remove(field.name, field.scope, field.id);
     this.fields.remove(field);
     const flags = this.flags;
-    if (field.scope && flags[`$${field.scope}`]) {
+    if (!isNullOrUndefined(field.scope) && flags[`$${field.scope}`]) {
       delete flags[`$${field.scope}`][field.name];
-    } else if (!field.scope) {
+    } else if (isNullOrUndefined(field.scope)) {
       delete flags[field.name];
     }
 
@@ -318,17 +318,17 @@ export default class Validator {
   /**
    * Updates a field, updating both errors and flags.
    *
-   * @param {String} id 
-   * @param {Object} diff 
+   * @param {String} id
+   * @param {Object} diff
    */
   update (id, { scope }) {
     const field = this._resolveField(`#${id}`);
     this.errors.update(id, { scope });
 
     // remove old scope.
-    if (field.scope && this.flags[`$${field.scope}`]) {
+    if (!isNullOrUndefined(field.scope) && this.flags[`$${field.scope}`]) {
       delete this.flags[`$${field.scope}`][field.name];
-    } else if (!field.scope) {
+    } else if (isNullOrUndefined(field.scope)) {
       delete this.flags[field.name];
     }
 
@@ -567,11 +567,11 @@ export default class Validator {
 
   /**
    * Adds a field flags to the flags collection.
-   * @param {Field} field 
-   * @param {String|null} scope 
+   * @param {Field} field
+   * @param {String|null} scope
    */
   _addFlag (field, scope = null) {
-    if (!scope) {
+    if (isNullOrUndefined(scope)) {
       this.flags = assign({}, this.flags, { [`${field.name}`]: field.flags });
       return;
     }
@@ -726,7 +726,7 @@ export default class Validator {
    * @return {Field}
    */
   _resolveField (name, scope) {
-    if (scope) {
+    if (!isNullOrUndefined(scope)) {
       return this.fields.find({ name, scope });
     }
 
@@ -754,7 +754,7 @@ export default class Validator {
   _handleFieldNotFound (name, scope) {
     if (!this.strict) return Promise.resolve(true);
 
-    const fullName = scope ? name : `${scope ? scope + '.' : ''}${name}`;
+    const fullName = isNullOrUndefined(scope) ? name : `${!isNullOrUndefined(scope) ? scope + '.' : ''}${name}`;
     throw createError(
       `Validating a non-existant field: "${fullName}". Use "attach()" first.`
     );


Test patch:
diff --git a/tests/field.js b/tests/field.js
index a9c9743a..622357e9 100644
--- a/tests/field.js
+++ b/tests/field.js
@@ -77,7 +77,7 @@ test('it adds value listeners on the native inputs', () => {
   expect(field.watchers.length).toBe(4); // still 4
 
   field.vm = { $validator: { validate: jest.fn() } };
-  
+
   // test input event.
   el.dispatchEvent(new Event('input'));
   expect(field.validator.validate).toHaveBeenCalledWith(`#${field.id}`, field.value);
@@ -195,7 +195,7 @@ test('it adds class listeners on the input', () => {
   expect(field.flags.dirty).toBe(true);
   expect(el.classList.contains('pristine')).toBe(false);
   expect(field.flags.pristine).toBe(false);
-  
+
   el.dispatchEvent(new Event('blur'));
   expect(field.watchers.length).toBe(2); // 1 more down.
   expect(el.classList.contains('untouched')).toBe(false);
@@ -351,7 +351,7 @@ test('sets aria attributes on elements', async () => {
   expect(el.getAttribute('aria-invalid')).toBe('true');
   field.el = null; // headless
   field.updateAriaAttrs();
-  
+
   // unchanged.
   expect(el.getAttribute('aria-required')).toBe('false');
   expect(el.getAttribute('aria-invalid')).toBe('true');
@@ -398,6 +398,19 @@ test('calls the update method on the validator errors when updating scope', () =
   expect(vm.$validator.update).toHaveBeenCalledTimes(1);
 });
 
+test('calls the update method on the validator errors when updating scope to 0', () => {
+  let el = document.createElement('input');
+  const vm = {
+    $validator: {
+      update: jest.fn()
+    }
+  };
+  const field = new Field(el, { rules: 'required', vm });
+  field.updated = true;
+  field.update({ rules: 'min:3', scope: 0 });
+  expect(vm.$validator.update).toHaveBeenCalledTimes(1);
+});
+
 describe('fields can track their dependencies', () => {
   test('native input depencies', () => {
     document.body.innerHTML = `
@@ -556,4 +569,9 @@ test('sets the field flags', () => {
 
   expect(field.flags.touched).toBe(false);
   expect(field.flags.untouched).toBe(false);
-});
\ No newline at end of file
+});
+
+test('field scope set to 0', () => {
+  const field = new Field(null, {scope:0});
+  expect(field.scope).toBe(0);
+});
diff --git a/tests/validator.js b/tests/validator.js
index 0f750aba..2b49817f 100644
--- a/tests/validator.js
+++ b/tests/validator.js
@@ -147,7 +147,7 @@ test('can set strict mode on specific instances', async () => {
   const v = new Validator({
   imp: 'required'
   });
-  
+
   try {
     let result = await v.validateAll({
       imp: 'Tyrion Lannister',
@@ -248,7 +248,7 @@ test('fails when trying to validate a non-existant field when strict mode is tru
     content: 'required|max:20',
     tags: 'required|in:1,2,3,5'
   });
-  
+
   expect(() => v.validate('nonExistant', 'whatever')).toThrow();
 });
 
@@ -354,7 +354,7 @@ test('can add a custom validator with localized messages', async () => {
 
   v.setLocale('ar');
   expect(v.locale).toBe('ar');
-  
+
   expect(await v.validate('anotherField', 1)).toBe(false);
   expect(v.errors.first('anotherField')).toBe('Some Arabic Text');
 });
@@ -572,7 +572,7 @@ test('without promises', async () => {
   });
 
   v.attach('field', 'reason');
-  
+
     expect(await v.validate('field', 'wow')).toBe(false);
 
 
@@ -615,11 +615,11 @@ test('using promises', async () => {
     }
   });
   v.attach('reason_field', 'reason_test');
-  
+
     expect(await v.validate('reason_field', 'trigger')).toBe(false);
 
   expect(v.errors.first('reason_field')).toBe('Not this value');
-  
+
     expect(await v.validate('reason_field', false)).toBe(false);
 
   expect(v.errors.first('reason_field')).toBe('Something went wrong');
@@ -628,7 +628,7 @@ test('using promises', async () => {
 test('can remove rules from the list of validators', async () => {
   Validator.extend('dummy', (value) => !! value);
   const v1 = new Validator({ name: 'dummy'});
-  
+
     await v1.validate('name', false);
 
   v1.remove('dummy');
@@ -684,7 +684,7 @@ test('can fetch the values using getters for a specific scope when not specifyin
   v1.attach('name', 'required|alpha', { scope: 'scope2', getter:getter2 });
 
   expect(await v1.validateAll('scope1')).toBe(true);
-  expect(await v1.validateAll('scope2')).toBe(false);    
+  expect(await v1.validateAll('scope2')).toBe(false);
 });
 
 test('ignores empty rules', async () => {
@@ -710,7 +710,7 @@ test('validations can be paused and resumed', async () => {
   expect(await v.validate('name', '')).toBe(true);
   expect(await v.validateAll()).toBe(true);
   expect(await v.validateScopes()).toBe(true);
-  
+
   v.resume();
   expect(await v.validate('name', '')).toBe(false);
 
@@ -817,8 +817,10 @@ test('validate can resolve the value if it was not provided', async () => {
 
 test('resolves a field by name and scope', async () => {
   const v = new Validator();
-  const field = v.attach('field', 'alpha', { scope: 's1' });
+  let field = v.attach('field', 'alpha', { scope: 's1' });
   expect(v._resolveField('field', 's1')).toBe(field);
+  field = v.attach('field', 'alpha', { scope: 0 });
+  expect(v._resolveField('field', 0)).toBe(field);
 });
 
 test('handles unresolved fields when strict is false by returning true', async () => {
@@ -976,4 +978,4 @@ test('updates existing field errors and flags to match the new scope', () => {
   // test flag and errors updates.
   expect(v.flags.$s2.email).toBeTruthy();
   expect(v.errors.first('s2.email')).toBeTruthy();
-});
\ No newline at end of file
+});