Ver Fonte

Merge branch 'master' into test/imporve-table

qingwei.li há 8 anos atrás
pai
commit
ad30305d6d

+ 2 - 2
examples/docs/zh-cn/form.md

@@ -792,8 +792,8 @@
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | model   | 表单数据对象 | object      |                  —                |  — |
 | rules    | 表单验证规则 | object | — | — |
-| type | 表单类型 | string |  stacked, inline, horizontal | horizontal |
-| label-align | 表单域标签的水平对齐位置 | string |  right,left            | right |
+| inline    | 行内表单模式 | boolean | — | false |
+| label-position | 表单域标签的位置 | string |  right/left/top            | right |
 | label-width | 表单域标签的宽度,所有的 form-item 都会继承 form 组件的 labelWidth 的值 | string | — | — |
 | label-suffix | 表单域标签的后缀 | string | — | — |
 

+ 3 - 1
examples/docs/zh-cn/table.md

@@ -918,6 +918,7 @@
 | cell-mouse-leave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
 | cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
 | row-click | 当某一行被点击时会触发该事件 | row, event |
+| sort-change | 当表格的排序条件发生变化的时候会触发该事件 | { column, prop, order } |
 
 ### Table Methods
 | 方法名 | 说明 | 参数 |
@@ -931,7 +932,8 @@
 | prop | 对应列内容的字段名,也可以使用 property 属性 | string | — | — |
 | width | 对应列的宽度 | string | — | — |
 | fixed | 列是否固定在左侧或者右侧,true 表示固定在左侧 | string, boolean | true, left, right | - |
-| sortable | 对应列是否可以排序 | boolean | — | false |
+| sortable | 对应列是否可以排序,如果设置为 'custom',则代表用户希望远程排序,需要监听 Table 的 sort-change 事件 | boolean, string | true, false, 'custom' | false |
+| sort-method | 对数据进行排序的时候使用的方法,仅当 sortable 设置为 true 的时候有效 | Function(a, b) | - | - |
 | resizable | 对应列是否可以通过拖动改变宽度(如果需要在 el-table 上设置 border 属性为真) | boolean | — | true |
 | type | 对应列的类型。如果设置了 `selection` 则显示多选框,如果设置了 `index` 则显示该行的索引(从 1 开始计算) | string | selection/index | — |
 | formatter | 用来格式化内容 | Function(row, column) | — | — |

+ 0 - 1
packages/form/src/form-item.vue

@@ -85,7 +85,6 @@
     methods: {
       validate(trigger, cb) {
         var rules = this.getFilteredRule(trigger);
-
         if (!rules || rules.length === 0) {
           cb && cb();
           return true;

+ 1 - 1
packages/form/src/form.vue

@@ -15,7 +15,6 @@
     props: {
       model: Object,
       rules: Object,
-      type: String,
       labelPosition: String,
       labelWidth: String,
       labelSuffix: {
@@ -35,6 +34,7 @@
         this.fields[field.prop] = field;
         this.fieldLength++;
       });
+      /* istanbul ignore next */
       this.$on('el.form.removeField', (field) => {
         delete this.fields[field.prop];
         this.fieldLength--;

+ 6 - 4
packages/table/src/table-column.js

@@ -6,19 +6,19 @@ let columnIdSeed = 1;
 
 const defaults = {
   default: {
-    direction: ''
+    order: ''
   },
   selection: {
     width: 48,
     minWidth: 48,
     realWidth: 48,
-    direction: ''
+    order: ''
   },
   index: {
     width: 48,
     minWidth: 48,
     realWidth: 48,
-    direction: ''
+    order: ''
   }
 };
 
@@ -98,9 +98,10 @@ export default {
     minWidth: {},
     template: String,
     sortable: {
-      type: Boolean,
+      type: [Boolean, String],
       default: false
     },
+    sortMethod: Function,
     resizable: {
       type: Boolean,
       default: true
@@ -201,6 +202,7 @@ export default {
       isColumnGroup,
       align: this.align ? 'is-' + this.align : null,
       sortable: this.sortable,
+      sortMethod: this.sortMethod,
       resizable: this.resizable,
       showTooltipWhenOverflow: this.showTooltipWhenOverflow,
       formatter: this.formatter,

+ 19 - 15
packages/table/src/table-header.js

@@ -33,7 +33,7 @@ export default {
                   on-mousemove={ ($event) => this.handleMouseMove($event, column) }
                   on-mouseout={ this.handleMouseOut }
                   on-mousedown={ ($event) => this.handleMouseDown($event, column) }
-                  class={ [column.id, column.direction, column.align, this.isCellHidden(cellIndex) ? 'hidden' : ''] }>
+                  class={ [column.id, column.order, column.align, this.isCellHidden(cellIndex) ? 'hidden' : ''] }>
                   <div class={ ['cell', column.filteredValue && column.filteredValue.length > 0 ? 'highlight' : ''] }>
                   {
                     column.headerTemplate
@@ -269,26 +269,30 @@ export default {
 
       if (!column.sortable) return;
 
-      const sortCondition = this.store.states.sortCondition;
+      const states = this.store.states;
+      let sortProp = states.sortProp;
+      let sortOrder;
+      const sortingColumn = states.sortingColumn;
 
-      if (sortCondition.column !== column) {
-        if (sortCondition.column) {
-          sortCondition.column.direction = '';
+      if (sortingColumn !== column) {
+        if (sortingColumn) {
+          sortingColumn.order = null;
         }
-        sortCondition.column = column;
-        sortCondition.property = column.property;
+        states.sortingColumn = column;
+        sortProp = column.property;
       }
 
-      if (!column.direction) {
-        column.direction = 'ascending';
-      } else if (column.direction === 'ascending') {
-        column.direction = 'descending';
+      if (!column.order) {
+        sortOrder = column.order = 'ascending';
+      } else if (column.order === 'ascending') {
+        sortOrder = column.order = 'descending';
       } else {
-        column.direction = '';
-        sortCondition.column = null;
-        sortCondition.property = null;
+        sortOrder = column.order = null;
+        states.sortingColumn = null;
+        sortProp = null;
       }
-      sortCondition.direction = column.direction === 'descending' ? -1 : 1;
+      states.sortProp = sortProp;
+      states.sortOrder = sortOrder;
 
       this.store.commit('changeSortCondition');
     }

+ 20 - 8
packages/table/src/table-store.js

@@ -11,6 +11,14 @@ const getRowIdentity = (row, rowKey) => {
   }
 };
 
+const sortData = (data, states) => {
+  const sortingColumn = states.sortingColumn;
+  if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
+    return data;
+  }
+  return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod);
+};
+
 const TableStore = function(table, initialState = {}) {
   if (!table) {
     throw new Error('Table is required.');
@@ -26,11 +34,9 @@ const TableStore = function(table, initialState = {}) {
     _data: null,
     filteredData: null,
     data: null,
-    sortCondition: {
-      column: null,
-      property: null,
-      direction: null
-    },
+    sortingColumn: null,
+    sortProp: null,
+    sortOrder: null,
     isAllSelected: false,
     selection: [],
     reserveSelection: false,
@@ -52,7 +58,7 @@ TableStore.prototype.mutations = {
     if (data && data[0] && typeof data[0].$selected === 'undefined') {
       data.forEach((item) => Vue.set(item, '$selected', false));
     }
-    states.data = orderBy((data || []), states.sortCondition.property, states.sortCondition.direction);
+    states.data = sortData((data || []), states);
 
     if (!states.reserveSelection) {
       states.isAllSelected = false;
@@ -82,7 +88,13 @@ TableStore.prototype.mutations = {
   },
 
   changeSortCondition(states) {
-    states.data = orderBy((states.filteredData || states._data || []), states.sortCondition.property, states.sortCondition.direction);
+    states.data = sortData((states.filteredData || states._data || []), states);
+
+    this.table.$emit('sort-change', {
+      column: this.states.sortingColumn,
+      prop: this.states.sortProp,
+      order: this.states.sortOrder
+    });
 
     Vue.nextTick(() => this.table.updateScrollY());
   },
@@ -113,7 +125,7 @@ TableStore.prototype.mutations = {
     });
 
     states.filteredData = data;
-    states.data = orderBy(data, states.sortCondition.property, states.sortCondition.direction);
+    states.data = sortData(data, states);
 
     Vue.nextTick(() => this.table.updateScrollY());
   },

+ 7 - 2
packages/table/src/util.js

@@ -58,14 +58,19 @@ const isObject = function(obj) {
   return obj !== null && typeof obj === 'object';
 };
 
-export const orderBy = function(array, sortKey, reverse) {
+export const orderBy = function(array, sortKey, reverse, sortMethod) {
+  if (typeof reverse === 'string') {
+    reverse = reverse === 'descending' ? -1 : 1;
+  }
   if (!sortKey) {
     return array;
   }
   const order = (reverse && reverse < 0) ? -1 : 1;
 
   // sort on a copy to avoid mutating original array
-  return array.slice().sort(function(a, b) {
+  return array.slice().sort(sortMethod ? function(a, b) {
+    return sortMethod(a, b) ? order : -order;
+  } : function(a, b) {
     if (sortKey !== '$key') {
       if (isObject(a) && '$value' in a) a = a.$value;
       if (isObject(b) && '$value' in b) b = b.$value;

+ 6 - 6
test/unit/specs/dropdown.spec.js

@@ -4,7 +4,7 @@ describe('Dropdown', () => {
   it('create', done => {
     const vm = createVue({
       template: `
-        <el-dropdown>
+        <el-dropdown ref="dropdown">
           <span class="el-dropdown-link">
             下拉菜单<i class="el-icon-caret-bottom el-icon-right"></i>
           </span>
@@ -18,19 +18,19 @@ describe('Dropdown', () => {
         </el-dropdown>
       `
     }, true);
-    let dropdownElm = vm.$el;
+    let dropdown = vm.$refs.dropdown;
+    let dropdownElm = dropdown.$el;
     let triggerElm = dropdownElm.children[0];
 
     triggerEvent(triggerElm, 'mouseenter');
     setTimeout(_ => {
-      var dropdownMenu = document.querySelector('.dropdown-test-creat');
-      expect(dropdownMenu.style.display).to.not.ok;
+      expect(dropdown.visible).to.be.true;
 
       triggerEvent(triggerElm, 'mouseleave');
       setTimeout(_ => {
-        expect(dropdownMenu.style.display).to.be.equal('none');
+        expect(dropdown.visible).to.not.true;
         done();
-      }, 600);
+      }, 300);
     }, 400);
   });
   it('menu click', done => {

+ 577 - 0
test/unit/specs/form.spec.js

@@ -0,0 +1,577 @@
+import { createVue } from '../util';
+
+describe('Form', () => {
+  it('label width', done => {
+    const vm = createVue({
+      template: `
+        <el-form ref="form" :model="form" label-width="80px">
+          <el-form-item label="活动名称">
+            <el-input v-model="form.name"></el-input>
+          </el-form-item>
+        </el-form>
+      `,
+      data() {
+        return {
+          form: {
+            name: ''
+          }
+        };
+      }
+    }, true);
+    expect(vm.$el.querySelector('.el-form-item__label').style.width).to.equal('80px');
+    expect(vm.$el.querySelector('.el-form-item__content').style.marginLeft).to.equal('80px');
+    done();
+  });
+  it('inline form', done => {
+    const vm = createVue({
+      template: `
+        <el-form ref="form" :model="form" inline>
+          <el-form-item>
+            <el-input v-model="form.name"></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-input v-model="form.address"></el-input>
+          </el-form-item>
+        </el-form>
+      `,
+      data() {
+        return {
+          form: {
+            name: '',
+            address: ''
+          }
+        };
+      }
+    }, true);
+    expect(vm.$el.classList.contains('el-form--inline')).to.be.true;
+    done();
+  });
+  it('label position', done => {
+    const vm = createVue({
+      template: `
+        <div>
+          <el-form :model="form" label-position="top" ref="labelTop">
+            <el-form-item>
+              <el-input v-model="form.name"></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-input v-model="form.address"></el-input>
+            </el-form-item>
+          </el-form>
+          <el-form :model="form" label-position="left" ref="labelLeft">
+            <el-form-item>
+              <el-input v-model="form.name"></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-input v-model="form.address"></el-input>
+            </el-form-item>
+          </el-form>
+        </div>
+      `,
+      data() {
+        return {
+          form: {
+            name: '',
+            address: ''
+          }
+        };
+      }
+    }, true);
+    expect(vm.$refs.labelTop.$el.classList.contains('el-form--label-top')).to.be.true;
+    expect(vm.$refs.labelLeft.$el.classList.contains('el-form--label-left')).to.be.true;
+    done();
+  });
+  it('reset field', done => {
+    const vm = createVue({
+      template: `
+        <el-form ref="form" :model="form" :rules="rules">
+          <el-form-item label="活动名称" prop="name">
+            <el-input v-model="form.name" ref="fieldName"></el-input>
+          </el-form-item>
+          <el-form-item label="活动地址" prop="address">
+            <el-input v-model="form.address" ref="fieldAddr"></el-input>
+          </el-form-item>
+          <el-form-item label="活动性质" prop="type">
+            <el-checkbox-group v-model="form.type">
+              <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
+              <el-checkbox label="地推活动" name="type"></el-checkbox>
+              <el-checkbox label="线下主题活动" name="type"></el-checkbox>
+              <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
+            </el-checkbox-group>
+          </el-form-item>
+        </el-form>
+      `,
+      data() {
+        return {
+          form: {
+            name: '',
+            address: '',
+            type: []
+          },
+          rules: {
+            name: [
+              { required: true, message: '请输入活动名称', trigger: 'blur' }
+            ],
+            address: [
+              { required: true, message: '请选择活动区域', trigger: 'change' }
+            ],
+            type: [
+              { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
+            ]
+          }
+        };
+      },
+      methods: {
+        setValue() {
+          this.form.name = 'jack';
+          this.form.address = 'aaaa';
+          this.form.type.push('地推活动');
+        }
+      }
+    }, true);
+    vm.setValue();
+    vm.$refs.form.resetFields();
+    vm.$refs.form.$nextTick(_ => {
+      expect(vm.form.name).to.equal('');
+      expect(vm.form.address).to.equal('');
+      expect(vm.form.type.length).to.equal(0);
+      done();
+    });
+  });
+  it('form item nest', done => {
+    const vm = createVue({
+      template: `
+        <el-form ref="form" :model="form" :rules="rules">
+          <el-form-item label="活动时间" required>
+            <el-col :span="11">
+              <el-form-item prop="date1">
+                <el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col class="line" :span="2">-</el-col>
+            <el-col :span="11">
+              <el-form-item prop="date2">
+                <el-time-picker type="fixed-time" placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
+              </el-form-item>
+            </el-col>
+          </el-form-item>
+        </el-form>
+      `,
+      data() {
+        return {
+          form: {
+            date1: '',
+            date2: ''
+          },
+          rules: {
+            date1: [
+              { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
+            ]
+          }
+        };
+      },
+      methods: {
+        setValue() {
+          this.name = 'jack';
+          this.address = 'aaaa';
+        }
+      }
+    }, true);
+    vm.$refs.form.validate(valid => {
+      expect(valid).to.not.true;
+      done();
+    });
+  });
+  describe('validate', () => {
+    it('input', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="活动名称" prop="name">
+              <el-input v-model="form.name"></el-input>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              name: ''
+            },
+            rules: {
+              name: [
+                { required: true, message: '请输入活动名称', trigger: 'change', min: 3, max: 6 }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.name = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.name.error).to.equal('请输入活动名称');
+          vm.setValue('aaaaa');
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.name.error).to.equal('');
+            vm.setValue('aa');
+
+            vm.$refs.form.$nextTick(_ => {
+              expect(fields.name.error).to.equal('请输入活动名称');
+              done();
+            });
+          });
+        });
+      });
+    });
+    it('textarea', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="活动名称" prop="name">
+              <el-input type="textarea" v-model="form.name"></el-input>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              name: ''
+            },
+            rules: {
+              name: [
+                { required: true, message: '请输入活动名称', trigger: 'change', min: 3, max: 6 }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.name = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.name.error).to.equal('请输入活动名称');
+          vm.setValue('aaaaa');
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.name.error).to.equal('');
+            vm.setValue('aa');
+
+            vm.$refs.form.$nextTick(_ => {
+              expect(fields.name.error).to.equal('请输入活动名称');
+              done();
+            });
+          });
+        });
+      });
+    });
+    it('selector', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="记住密码" prop="region">
+              <el-select v-model="form.region" placeholder="请选择活动区域">
+                <el-option label="区域一" value="shanghai"></el-option>
+                <el-option label="区域二" value="beijing"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              region: 'shanghai'
+            },
+            rules: {
+              region: [
+                {required: true, message: '请选择活动区域', trigger: 'change' }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.region = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.true;
+        vm.setValue('');
+        setTimeout(_ => {
+          expect(fields.region.error).to.equal('请选择活动区域');
+          vm.setValue('shanghai');
+
+          setTimeout(_ => {
+            expect(fields.region.error).to.equal('');
+            done();
+          }, 100);
+        }, 100);
+      });
+    });
+    it('datepicker', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="记住密码" prop="date">
+              <el-date-picker type="date" placeholder="选择日期" v-model="form.date" style="width: 100%;"></el-date-picker>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              date: ''
+            },
+            rules: {
+              date: [
+                {type: 'date', required: true, message: '请选择日期', trigger: 'change' }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.date = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.date.error).to.equal('请选择日期');
+
+          vm.setValue(new Date());
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.date.error).to.equal('');
+            done();
+          });
+        });
+      });
+    });
+    it('timepicker', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="记住密码" prop="date">
+              <el-time-picker type="fixed-time" placeholder="选择时间" v-model="form.date" style="width: 100%;"></el-time-picker>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              date: ''
+            },
+            rules: {
+              date: [
+                {type: 'date', required: true, message: '请选择时间', trigger: 'change' }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.date = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.date.error).to.equal('请选择时间');
+          vm.setValue(new Date());
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.date.error).to.equal('');
+            done();
+          });
+        });
+      });
+    });
+    it('checkbox group', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="活动名称" prop="type">
+              <el-checkbox-group v-model="form.type">
+                <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
+                <el-checkbox label="地推活动" name="type"></el-checkbox>
+                <el-checkbox label="线下主题活动" name="type"></el-checkbox>
+                <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
+              </el-checkbox-group>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              type: []
+            },
+            rules: {
+              type: [
+                { type: 'array', required: true, message: '请选择活动类型', trigger: 'change' }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.type = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.type.error).to.equal('请选择活动类型');
+          vm.setValue(['地推活动']);
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.type.error).to.equal('');
+            done();
+          });
+        });
+      });
+    });
+    it('radio group', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="活动名称" prop="type">
+              <el-radio-group v-model="form.type">
+                <el-radio label="线上品牌商赞助"></el-radio>
+                <el-radio label="线下场地免费"></el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              type: ''
+            },
+            rules: {
+              type: [
+                { required: true, message: '请选择活动类型', trigger: 'change' }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.type = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.type.error).to.equal('请选择活动类型');
+          vm.setValue('线下场地免费');
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.type.error).to.equal('');
+            done();
+          });
+        });
+      });
+    });
+    it('validate field', done => {
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="活动名称" prop="name">
+              <el-input v-model="form.name"></el-input>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              name: ''
+            },
+            rules: {
+              name: [
+                { required: true, message: '请输入活动名称', trigger: 'change', min: 3, max: 6 }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.name = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validateField('name', valid => {
+        expect(valid).to.not.true;
+        done();
+      });
+    });
+    it('custom validate', done => {
+      var checkName = (rule, value, callback) => {
+        if (value.length < 5) {
+          callback(new Error('长度至少为5'));
+        } else {
+          callback();
+        }
+      };
+      const vm = createVue({
+        template: `
+          <el-form :model="form" :rules="rules" ref="form">
+            <el-form-item label="活动名称" prop="name">
+              <el-input v-model="form.name"></el-input>
+            </el-form-item>
+          </el-form>
+        `,
+        data() {
+          return {
+            form: {
+              name: ''
+            },
+            rules: {
+              name: [
+                { validator: checkName, trigger: 'change' }
+              ]
+            }
+          };
+        },
+        methods: {
+          setValue(value) {
+            this.form.name = value;
+          }
+        }
+      }, true);
+      vm.$refs.form.validate(valid => {
+        let fields = vm.$refs.form.fields;
+        expect(valid).to.not.true;
+        vm.$refs.form.$nextTick(_ => {
+          expect(fields.name.error).to.equal('长度至少为5');
+          vm.setValue('aaaaaa');
+
+          vm.$refs.form.$nextTick(_ => {
+            expect(fields.name.error).to.equal('');
+            done();
+          });
+        });
+      });
+    });
+  });
+});