Repository: vuejs/vue
Pull request: feature: allow component prop type to be an array of types

Solves #2337


Changed files:
filename: src/compiler/compile-props.js
patch: @@ -278,36 +278,23 @@ export function assertProp (prop, value) {
   }
   var options = prop.options
   var type = options.type
-  var valid = true
-  var expectedType
+  var valid
+  var expectedTypes = []
   if (type) {
-    if (type === String) {
-      expectedType = 'string'
-      valid = typeof value === expectedType
-    } else if (type === Number) {
-      expectedType = 'number'
-      valid = typeof value === 'number'
-    } else if (type === Boolean) {
-      expectedType = 'boolean'
-      valid = typeof value === 'boolean'
-    } else if (type === Function) {
-      expectedType = 'function'
-      valid = typeof value === 'function'
-    } else if (type === Object) {
-      expectedType = 'object'
-      valid = isPlainObject(value)
-    } else if (type === Array) {
-      expectedType = 'array'
-      valid = isArray(value)
-    } else {
-      valid = value instanceof type
+    if (!isArray(type)) {
+      type = [ type ]
+    }
+    for (var i = 0; i < type.length && !valid; i++) {
+      var assertedType = assertType(value, type[i])
+      expectedTypes.push(assertedType.expectedType)
+      valid = assertedType.valid
     }
   }
-  if (!valid) {
+  if (typeof valid !== 'undefined' && !valid) {
     process.env.NODE_ENV !== 'production' && warn(
       'Invalid prop: type check failed for ' +
       prop.path + '="' + prop.raw + '".' +
-      ' Expected ' + formatType(expectedType) +
+      ' Expected ' + formatTypes(expectedTypes) +
       ', got ' + formatValue(value) + '.'
     )
     return false
@@ -342,9 +329,46 @@ export function coerceProp (prop, value) {
   return coerce(value)
 }
 
-function formatType (val) {
-  return val
-    ? val.charAt(0).toUpperCase() + val.slice(1)
+function assertType (value, type) {
+  var valid
+  var expectedType
+  if (type === String) {
+    expectedType = 'string'
+    valid = typeof value === expectedType
+  } else if (type === Number) {
+    expectedType = 'number'
+    valid = typeof value === expectedType
+  } else if (type === Boolean) {
+    expectedType = 'boolean'
+    valid = typeof value === expectedType
+  } else if (type === Function) {
+    expectedType = 'function'
+    valid = typeof value === expectedType
+  } else if (type === Object) {
+    expectedType = 'object'
+    valid = isPlainObject(value)
+  } else if (type === Array) {
+    expectedType = 'array'
+    valid = isArray(value)
+  } else {
+    valid = value instanceof type
+  }
+  return {
+    valid,
+    expectedType
+  }
+}
+
+function formatTypes (types) {
+  return types.reduce(
+    (p, c, i) => { return i === 0 ? formatType(c) : `${p}, ${formatType(c)}` },
+    ''
+  )
+}
+
+function formatType (type) {
+  return type
+    ? type.charAt(0).toUpperCase() + type.slice(1)
     : 'custom type'
 }
 

filename: test/unit/specs/directives/internal/prop_spec.js
patch: @@ -364,6 +364,13 @@ describe('prop', function () {
       expect('Expected custom type').toHaveBeenWarned()
     })
 
+    it('multiple types', function () {
+      makeInstance([], [Array, Number, Boolean])
+      expect(getWarnCount()).toBe(0)
+      makeInstance({}, [Array, Number, Boolean])
+      expect('Expected Array, Number, Boolean').toHaveBeenWarned()
+    })
+
     it('custom validator', function () {
       makeInstance(123, null, function (v) {
         return v === 123
@@ -390,6 +397,21 @@ describe('prop', function () {
       expect('Expected String').toHaveBeenWarned()
     })
 
+    it('multiple types + custom validator', function () {
+      makeInstance(123, [Number, String, Boolean], function (v) {
+        return v === 123
+      })
+      expect(getWarnCount()).toBe(0)
+      makeInstance(123, [Number, String, Boolean], function (v) {
+        return v === 234
+      })
+      expect('custom validator check failed').toHaveBeenWarned()
+      makeInstance(123, [String, Boolean], function (v) {
+        return v === 123
+      })
+      expect('Expected String, Boolean').toHaveBeenWarned()
+    })
+
     it('type check + coerce', function () {
       makeInstance(123, String, null, String)
       expect(getWarnCount()).toBe(0)
@@ -401,6 +423,17 @@ describe('prop', function () {
       expect(getWarnCount()).toBe(0)
     })
 
+    it('multiple types + custom validator', function () {
+      makeInstance(123, [String, Boolean, Number], null, String)
+      expect(getWarnCount()).toBe(0)
+      makeInstance('123', [String, Boolean, Number], null, Number)
+      expect(getWarnCount()).toBe(0)
+      makeInstance('123', [String, Boolean, Function], null, function (val) {
+        return val === '123'
+      })
+      expect(getWarnCount()).toBe(0)
+    })
+
     it('required', function () {
       new Vue({
         el: document.createElement('div'),