Repository: baianat/vee-validate
Pull request #2034 (2019-04-18 10:05:46)

fix(rules): improve file/files handling
🔎 __Overview__

This is a follow-up PR to #2031 and introduces a new `ensureArray()` util to create an array from a single object like an `File` or an array-like object like an `FileList`.

Tests for the new util are also added and it's used in any rule that handles files.

codecov[bot]: # [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/2034?src=pr&el=h1) Report
> Merging [#2034](https://codecov.io/gh/baianat/vee-validate/pull/2034?src=pr&el=desc) into [master](https://codecov.io/gh/baianat/vee-validate/commit/482c643b99546173845c01d4b0af651ae494c050?src=pr&el=desc) will **decrease** coverage by `<.01%`.
> The diff coverage is `100%`.

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

```diff
@@            Coverage Diff             @@
##           master    #2034      +/-   ##
==========================================
- Coverage   96.01%   96.01%   -0.01%     
==========================================
  Files          62       62              
  Lines        2358     2357       -1     
  Branches      741      738       -3     
==========================================
- Hits         2264     2263       -1     
  Misses         85       85              
  Partials        9        9
```


| [Impacted Files](https://codecov.io/gh/baianat/vee-validate/pull/2034?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/rules/ext.js](https://codecov.io/gh/baianat/vee-validate/pull/2034/diff?src=pr&el=tree#diff-c3JjL3J1bGVzL2V4dC5qcw==) | `100% <100%> (ø)` | :arrow_up: |
| [src/utils/index.js](https://codecov.io/gh/baianat/vee-validate/pull/2034/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2luZGV4Lmpz) | `97.81% <100%> (+0.04%)` | :arrow_up: |
| [src/rules/mimes.js](https://codecov.io/gh/baianat/vee-validate/pull/2034/diff?src=pr&el=tree#diff-c3JjL3J1bGVzL21pbWVzLmpz) | `100% <100%> (ø)` | :arrow_up: |
| [src/rules/size.js](https://codecov.io/gh/baianat/vee-validate/pull/2034/diff?src=pr&el=tree#diff-c3JjL3J1bGVzL3NpemUuanM=) | `100% <100%> (ø)` | :arrow_up: |
| [src/rules/dimensions.js](https://codecov.io/gh/baianat/vee-validate/pull/2034/diff?src=pr&el=tree#diff-c3JjL3J1bGVzL2RpbWVuc2lvbnMuanM=) | `100% <100%> (ø)` | :arrow_up: |

------

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


Patch:
diff --git a/src/rules/dimensions.js b/src/rules/dimensions.js
index 9d997c9c..d3e937cf 100644
--- a/src/rules/dimensions.js
+++ b/src/rules/dimensions.js
@@ -1,3 +1,7 @@
+import { ensureArray } from '../utils';
+
+const imageRegex = /\.(jpg|svg|jpeg|png|bmp|gif)$/i;
+
 const validateImage = (file, width, height) => {
   const URL = window.URL || window.webkitURL;
   return new Promise(resolve => {
@@ -12,18 +16,11 @@ const validateImage = (file, width, height) => {
 };
 
 const validate = (files, [width, height]) => {
-  if (!Array.isArray(files)) files = [files];
-  const list = [];
-  for (let i = 0; i < files.length; i++) {
-    // if file is not an image, reject.
-    if (! /\.(jpg|svg|jpeg|png|bmp|gif)$/i.test(files[i].name)) {
-      return false;
-    }
-
-    list.push(files[i]);
+  const images = ensureArray(files).filter(file => imageRegex.test(file.name));
+  if (images.length === 0) {
+    return false;
   }
-
-  return Promise.all(list.map(file => validateImage(file, width, height)));
+  return Promise.all(images.map(image => validateImage(image, width, height)));
 };
 
 export {
diff --git a/src/rules/ext.js b/src/rules/ext.js
index fa3a514b..a5ebde7a 100644
--- a/src/rules/ext.js
+++ b/src/rules/ext.js
@@ -1,7 +1,8 @@
+import { ensureArray } from '../utils';
+
 const validate = (files, extensions) => {
   const regex = new RegExp(`.(${extensions.join('|')})$`, 'i');
-
-  return (Array.isArray(files) ? files : [files]).every(file => regex.test(file.name));
+  return ensureArray(files).every(file => regex.test(file.name));
 };
 
 export {
diff --git a/src/rules/mimes.js b/src/rules/mimes.js
index a9bd7fb4..d3d555fa 100644
--- a/src/rules/mimes.js
+++ b/src/rules/mimes.js
@@ -1,7 +1,8 @@
+import { ensureArray } from '../utils';
+
 const validate = (files, mimes) => {
   const regex = new RegExp(`${mimes.join('|').replace('*', '.+')}$`, 'i');
-
-  return (Array.isArray(files) ? files : [files]).every(file => regex.test(file.type));
+  return ensureArray(files).every(file => regex.test(file.type));
 };
 
 export {
diff --git a/src/rules/size.js b/src/rules/size.js
index f8333803..67a80439 100644
--- a/src/rules/size.js
+++ b/src/rules/size.js
@@ -1,17 +1,11 @@
+import { ensureArray } from '../utils';
+
 const validate = (files, [size]) => {
-  if (!Array.isArray(files)) files = [files];
   if (isNaN(size)) {
     return false;
   }
-
   const nSize = Number(size) * 1024;
-  for (let i = 0; i < files.length; i++) {
-    if (files[i].size > nSize) {
-      return false;
-    }
-  }
-
-  return true;
+  return ensureArray(files).every(file => file.size <= nSize);
 };
 
 export {
diff --git a/src/utils/index.js b/src/utils/index.js
index 9ec885f7..3da38e49 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -380,6 +380,17 @@ export const toArray = (arrayLike: { length: number }) => {
   return array;
 };
 
+/**
+ * Converts an array-like object to array and place other elements in an array
+ */
+export const ensureArray = (arrayLike: any): array => {
+  if (Array.isArray(arrayLike)) {
+    return [...arrayLike];
+  }
+  const array = toArray(arrayLike);
+  return isEmptyArray(array) ? [arrayLike] : array;
+};
+
 /**
  * Assign polyfill from the mdn.
  */


Test patch:
diff --git a/tests/helpers/index.js b/tests/helpers/index.js
index 8799a4ed..ba580763 100644
--- a/tests/helpers/index.js
+++ b/tests/helpers/index.js
@@ -44,9 +44,10 @@ export default {
       }
     };
   },
-  file: (name, type, size = 1) => ({
-    name,
-    type,
-    size: size * 1024
+  file: (name, type, size = 1) => new File([new ArrayBuffer(size * 1024)], name, { type }),
+  fileList: (files) => ({
+    length: files.length,
+    item: (index) => files[index],
+    ...files
   })
 };
diff --git a/tests/unit/utils.js b/tests/unit/utils.js
index 2ae9cbc4..53cc55f9 100644
--- a/tests/unit/utils.js
+++ b/tests/unit/utils.js
@@ -2,6 +2,7 @@ import * as utils from '@/utils';
 import * as dateUtils from '@/utils/date';
 import * as i18Utils from '../../locale/utils';
 import * as eventUtils from '@/utils/events';
+import helpers from '../helpers';
 
 test('gets the data attribute prefixed with the plugin', () => {
   document.body.innerHTML =
@@ -198,6 +199,17 @@ test('converts array like objects to arrays', () => {
   expect(Array.isArray(array)).toBe(true);
 });
 
+test('converts file list or single file to array of files', () => {
+  const file = helpers.file('file.jpg', 'image/jpeg', 10);
+  expect(Array.isArray(file)).toBe(false);
+
+  const fileList = helpers.fileList([file]);
+  expect(Array.isArray(fileList)).toBe(false);
+
+  expect(Array.isArray(utils.ensureArray(file))).toBe(true);
+  expect(Array.isArray(utils.ensureArray(fileList))).toBe(true);
+});
+
 test('checks if a value path with exists', () => {
   const some = {
     value: {