diff --git a/components/anchor/__tests__/Anchor.test.js b/components/anchor/__tests__/Anchor.test.js
index 91b259319..60ab0e03c 100644
--- a/components/anchor/__tests__/Anchor.test.js
+++ b/components/anchor/__tests__/Anchor.test.js
@@ -18,8 +18,10 @@ describe('Anchor Render', () => {
     Vue.nextTick(() => {
       wrapper.find('a[href="#API"]').trigger('click')
       wrapper.vm.$refs.anchor.handleScroll()
-      expect(wrapper.vm.$refs.anchor.$data.activeLink).not.toBe(null)
-      done()
+      setTimeout(() => {
+        expect(wrapper.vm.$refs.anchor.$data.activeLink).not.toBe(null)
+        done()
+      }, 1000)
     })
   })
 
diff --git a/components/table/__tests__/Table.rowSelection.test.js b/components/table/__tests__/Table.rowSelection.test.js
new file mode 100644
index 000000000..c02cdc7bc
--- /dev/null
+++ b/components/table/__tests__/Table.rowSelection.test.js
@@ -0,0 +1,400 @@
+import Vue from 'vue'
+import { mount } from '@vue/test-utils'
+import { asyncExpect } from '@/tests/utils'
+import Table from '..'
+
+describe('Table.rowSelection', () => {
+  const columns = [{
+    title: 'Name',
+    dataIndex: 'name',
+  }]
+
+  const data = [
+    { key: 0, name: 'Jack' },
+    { key: 1, name: 'Lucy' },
+    { key: 2, name: 'Tom' },
+    { key: 3, name: 'Jerry' },
+  ]
+  function getTableOptions (props = {}, listeners = {}) {
+    return {
+      propsData: {
+        columns,
+        dataSource: data,
+        rowSelection: {},
+        ...props,
+      },
+      listeners: {
+        ...listeners,
+      },
+      sync: false,
+      attachedToDocument: true,
+    }
+  }
+  function renderedNames (wrapper) {
+    return wrapper.findAll({ name: 'TableRow' }).wrappers.map(row => {
+      return row.props().record.name
+    })
+  }
+
+  function getStore (wrapper) {
+    return wrapper.vm._vnode.componentInstance.store
+  }
+
+  it('select by checkbox', async () => {
+    const wrapper = mount(Table, getTableOptions())
+    const checkboxes = wrapper.findAll('input')
+    const checkboxAll = checkboxes.at(0)
+    checkboxAll.element.checked = true
+    checkboxAll.trigger('change')
+    await asyncExpect(() => {
+      expect(getStore(wrapper).getState()).toEqual({
+        selectedRowKeys: [0, 1, 2, 3],
+        selectionDirty: true,
+      })
+    })
+    checkboxes.at(1).element.checked = false
+    checkboxes.at(1).trigger('change')
+    await asyncExpect(() => {
+      expect(getStore(wrapper).getState()).toEqual({
+        selectedRowKeys: [1, 2, 3],
+        selectionDirty: true,
+      })
+    })
+    checkboxes.at(1).element.checked = true
+    checkboxes.at(1).trigger('change')
+    await asyncExpect(() => {
+      expect(getStore(wrapper).getState()).toEqual({
+        selectedRowKeys: [1, 2, 3, 0],
+        selectionDirty: true,
+      })
+    })
+  })
+
+  it('select by radio', async () => {
+    const wrapper = mount(Table, getTableOptions({ rowSelection: { type: 'radio' }}))
+    const radios = wrapper.findAll('input')
+
+    expect(radios.length).toBe(4)
+    radios.at(0).element.checked = true
+    radios.at(0).trigger('change')
+    await asyncExpect(() => {
+      expect(getStore(wrapper).getState()).toEqual({
+        selectedRowKeys: [0],
+        selectionDirty: true,
+      })
+    })
+    radios.at(radios.length - 1).element.checked = true
+    radios.at(radios.length - 1).trigger('change')
+    await asyncExpect(() => {
+      expect(getStore(wrapper).getState()).toEqual({
+        selectedRowKeys: [3],
+        selectionDirty: true,
+      })
+    })
+  })
+
+  it('pass getCheckboxProps to checkbox', () => {
+    const rowSelection = {
+      getCheckboxProps: record => ({ props: {
+        disabled: record.name === 'Lucy',
+        name: record.name,
+      }}),
+    }
+
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+    const checkboxes = wrapper.findAll('input').wrappers
+    expect(checkboxes[1].vnode.data.attrs.disabled).toBe(false)
+    expect(checkboxes[1].vnode.data.attrs.name).toEqual(data[0].name)
+    expect(checkboxes[2].vnode.data.attrs.disabled).toBe(true)
+    expect(checkboxes[2].vnode.data.attrs.name).toEqual(data[1].name)
+  })
+
+  it('works with pagination', async () => {
+    const wrapper = mount(Table, getTableOptions({ pagination: { pageSize: 2 }}))
+
+    const checkboxAll = wrapper.find({ name: 'SelectionCheckboxAll' })
+    const pagers = wrapper.findAll({ name: 'Pager' })
+    checkboxAll.find('input').element.checked = true
+    checkboxAll.find('input').trigger('change')
+    await asyncExpect(() => {
+      expect(checkboxAll.vm.$data).toEqual({ checked: true, indeterminate: false })
+    })
+    pagers.at(1).trigger('click')
+    await asyncExpect(() => {
+      expect(checkboxAll.vm.$data).toEqual({ checked: false, indeterminate: false })
+    })
+    pagers.at(0).trigger('click')
+    await asyncExpect(() => {
+      expect(checkboxAll.vm.$data).toEqual({ checked: true, indeterminate: false })
+    })
+  })
+
+  // https://github.com/ant-design/ant-design/issues/4020
+  it('handles defaultChecked', async () => {
+    const rowSelection = {
+      getCheckboxProps: record => ({
+        defaultChecked: record.key === 0,
+      }),
+    }
+
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+
+    await asyncExpect(() => {
+      const checkboxs = wrapper.findAll('input')
+      expect(checkboxs.at(1).vnode.data.domProps.checked).toBe(true)
+      expect(checkboxs.at(2).vnode.data.domProps.checked).toBe(false)
+      checkboxs.at(2).element.checked = true
+      checkboxs.at(2).trigger('change')
+    })
+
+    await asyncExpect(() => {
+      const checkboxs = wrapper.findAll('input')
+      expect(checkboxs.at(1).vnode.data.domProps.checked).toBe(true)
+      expect(checkboxs.at(2).vnode.data.domProps.checked).toBe(true)
+    })
+  })
+
+  it('can be controlled', async () => {
+    const wrapper = mount(Table, getTableOptions({ rowSelection: { selectedRowKeys: [0] }}))
+
+    expect(getStore(wrapper).getState()).toEqual({
+      selectedRowKeys: [0],
+      selectionDirty: false,
+    })
+
+    wrapper.setProps({ rowSelection: { selectedRowKeys: [1] }})
+    await asyncExpect(() => {
+      expect(getStore(wrapper).getState()).toEqual({
+        selectedRowKeys: [1],
+        selectionDirty: false,
+      })
+    })
+  })
+
+  it('fires change & select events', async () => {
+    const handleChange = jest.fn()
+    const handleSelect = jest.fn()
+    const rowSelection = {
+      onChange: handleChange,
+      onSelect: handleSelect,
+    }
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+    const checkboxs = wrapper.findAll('input')
+    checkboxs.at(checkboxs.length - 1).element.checked = true
+    checkboxs.at(checkboxs.length - 1).trigger('change')
+    await asyncExpect(() => {
+      expect(handleChange).toBeCalledWith([3], [{ key: 3, name: 'Jerry' }])
+      expect(handleSelect.mock.calls.length).toBe(1)
+      expect(handleSelect.mock.calls[0][0]).toEqual({ key: 3, name: 'Jerry' })
+      expect(handleSelect.mock.calls[0][1]).toEqual(true)
+      expect(handleSelect.mock.calls[0][2]).toEqual([{ key: 3, name: 'Jerry' }])
+    })
+  })
+
+  it('fires selectAll event', async () => {
+    const handleSelectAll = jest.fn()
+    const rowSelection = {
+      onSelectAll: handleSelectAll,
+    }
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+    const checkboxs = wrapper.findAll('input')
+    checkboxs.at(0).element.checked = true
+    checkboxs.at(0).trigger('change')
+    await asyncExpect(() => {
+      expect(handleSelectAll).toBeCalledWith(true, data, data)
+    })
+    checkboxs.at(0).element.checked = false
+    checkboxs.at(0).trigger('change')
+    await asyncExpect(() => {
+      expect(handleSelectAll).toBeCalledWith(false, [], data)
+    })
+  })
+
+  it('render with default selection correctly', async () => {
+    const rowSelection = {
+      selections: true,
+    }
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+    const dropdownWrapper = mount({
+      render () {
+        return wrapper.find({ name: 'Trigger' }).vm.getComponent()
+      },
+    }, { sync: false })
+    await asyncExpect(() => {
+      expect(dropdownWrapper.html()).toMatchSnapshot()
+    })
+
+    await asyncExpect(() => {
+
+    })
+  })
+
+  it('click select all selection', () => {
+    const handleSelectAll = jest.fn()
+    const rowSelection = {
+      onSelectAll: handleSelectAll,
+      selections: true,
+    }
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+
+    const dropdownWrapper = mount({
+      render () {
+        return wrapper.find({ name: 'Trigger' }).vm.getComponent()
+      },
+    }, { sync: false })
+    dropdownWrapper.findAll('.ant-dropdown-menu-item > div').at(0).trigger('click')
+
+    expect(handleSelectAll).toBeCalledWith(true, data, data)
+  })
+
+  it('fires selectInvert event', () => {
+    const handleSelectInvert = jest.fn()
+    const rowSelection = {
+      onSelectInvert: handleSelectInvert,
+      selections: true,
+    }
+    const wrapper = mount(Table, getTableOptions({ rowSelection }))
+    const checkboxes = wrapper.findAll('input')
+    checkboxes.at(1).element.checked = true
+    checkboxes.at(1).trigger('change')
+    const dropdownWrapper = mount({
+      render () {
+        return wrapper.find({ name: 'Trigger' }).vm.getComponent()
+      },
+    }, { sync: false })
+    const div = dropdownWrapper.findAll('.ant-dropdown-menu-item > div')
+    div.at(div.length - 1).trigger('click')
+
+    expect(handleSelectInvert).toBeCalledWith([1, 2, 3])
+  })
+
+  // it('fires selection event', () => {
+  //   const handleSelectOdd = jest.fn()
+  //   const handleSelectEven = jest.fn()
+  //   const rowSelection = {
+  //     selections: [{
+  //       key: 'odd',
+  //       text: '奇数项',
+  //       onSelect: handleSelectOdd,
+  //     }, {
+  //       key: 'even',
+  //       text: '偶数项',
+  //       onSelect: handleSelectEven,
+  //     }],
+  //   }
+  //   const wrapper = mount(Table, getTableOptions({ rowSelection }))
+
+  //   const dropdownWrapper = mount({
+  //     render () {
+  //       return wrapper.find({ name: 'Trigger' }).vm.getComponent()
+  //     },
+  //   })
+  //   expect(dropdownWrapper.find('.ant-dropdown-menu-item').length).toBe(4)
+
+  //   dropdownWrapper.find('.ant-dropdown-menu-item > div').at(2).trigger('click')
+  //   expect(handleSelectOdd).toBeCalledWith([0, 1, 2, 3])
+
+  //   dropdownWrapper.find('.ant-dropdown-menu-item > div').at(3).trigger('click')
+  //   expect(handleSelectEven).toBeCalledWith([0, 1, 2, 3])
+  // })
+
+  // it('could hide default selection options', () => {
+  //   const rowSelection = {
+  //     hideDefaultSelections: true,
+  //     selections: [{
+  //       key: 'odd',
+  //       text: '奇数项',
+  //     }, {
+  //       key: 'even',
+  //       text: '偶数项',
+  //     }],
+  //   }
+  //   const wrapper = mount(Table, getTableOptions({ rowSelection }))
+  //   const dropdownWrapper = mount({
+  //     render () {
+  //       return wrapper.find({ name: 'Trigger' }).vm.getComponent()
+  //     },
+  //   })
+  //   expect(dropdownWrapper.find('.ant-dropdown-menu-item').length).toBe(2)
+  // })
+
+  // it('handle custom selection onSelect correctly when hide default selection options', () => {
+  //   const handleSelectOdd = jest.fn()
+  //   const handleSelectEven = jest.fn()
+  //   const rowSelection = {
+  //     hideDefaultSelections: true,
+  //     selections: [{
+  //       key: 'odd',
+  //       text: '奇数项',
+  //       onSelect: handleSelectOdd,
+  //     }, {
+  //       key: 'even',
+  //       text: '偶数项',
+  //       onSelect: handleSelectEven,
+  //     }],
+  //   }
+  //   const wrapper = mount(Table, getTableOptions({ rowSelection }))
+
+  //   const dropdownWrapper = mount({
+  //     render () {
+  //       return wrapper.find({ name: 'Trigger' }).vm.getComponent()
+  //     },
+  //   })
+  //   expect(dropdownWrapper.find('.ant-dropdown-menu-item').length).toBe(2)
+
+  //   dropdownWrapper.find('.ant-dropdown-menu-item > div').at(0).trigger('click')
+  //   expect(handleSelectOdd).toBeCalledWith([0, 1, 2, 3])
+
+  //   dropdownWrapper.find('.ant-dropdown-menu-item > div').at(1).trigger('click')
+  //   expect(handleSelectEven).toBeCalledWith([0, 1, 2, 3])
+  // })
+
+  // // https://github.com/ant-design/ant-design/issues/4245
+  // it('handles disabled checkbox correctly when dataSource changes', () => {
+  //   const rowSelection = {
+  //     getCheckboxProps: record => ({ disabled: record.disabled }),
+  //   }
+  //   const wrapper = mount(Table, getTableOptions({ rowSelection }))
+  //   const newData = [
+  //     { key: 0, name: 'Jack', disabled: true },
+  //     { key: 1, name: 'Lucy', disabled: true },
+  //   ]
+  //   wrapper.setProps({ dataSource: newData })
+  //   wrapper.find('input').forEach((checkbox) => {
+  //     expect(checkbox.props().disabled).toBe(true)
+  //   })
+  // })
+
+  // // https://github.com/ant-design/ant-design/issues/4779
+  // it('should not switch pagination when select record', () => {
+  //   const newData = []
+  //   for (let i = 0; i < 20; i += 1) {
+  //     newData.push({
+  //       key: i.toString(),
+  //       name: i.toString(),
+  //     })
+  //   }
+  //   const wrapper = mount(Table, getTableOptions({
+  //     rowSelection: {},
+  //     dataSource: newData,
+  //   }))
+  //   wrapper.find('Pager').last().trigger('click') // switch to second page
+  //   wrapper.find('input').first().trigger('change', { target: { checked: true }})
+  //   wrapper.update()
+  //   expect(renderedNames(wrapper)).toEqual(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19'])
+  // })
+
+  // it('highlight selected row', () => {
+  //   const wrapper = mount(Table, getTableOptions())
+  //   wrapper.find('input').at(1).trigger('change', { target: { checked: true }})
+  //   expect(wrapper.find('tbody tr').at(0).hasClass('ant-table-row-selected')).toBe(true)
+  // })
+
+  // it('fix selection column on the left', () => {
+  //   const wrapper = mount(Table, getTableOptions({
+  //     rowSelection: { fixed: true },
+  //   }))
+
+  //   expect(wrapper).toMatchSnapshot()
+  // })
+})
diff --git a/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap b/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap
new file mode 100644
index 000000000..a30f3646f
--- /dev/null
+++ b/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Table.rowSelection render with default selection correctly 1`] = `
+<div>
+  <div class="ant-dropdown  ant-dropdown-placement-bottomLeft" style="display: none; left: -999px; top: -995px;">
+    <ul class="ant-dropdown-menu  ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light ant-table-selection-menu ant-dropdown-content" role="menu" aria-activedescendant="" tabindex="0">
+      <li role="menuitem" class="ant-dropdown-menu-item">
+        <div>Select current page</div>
+      </li>
+      <li role="menuitem" class="ant-dropdown-menu-item">
+        <div>Invert current page</div>
+      </li>
+    </ul>
+  </div>
+</div>
+`;