diff --git a/components/radio/__tests__/__snapshots__/group.test.js.snap b/components/radio/__tests__/__snapshots__/group.test.js.snap
new file mode 100644
index 000000000..ee7eabe77
--- /dev/null
+++ b/components/radio/__tests__/__snapshots__/group.test.js.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Radio all children should have a name property 1`] = `
+
+ A
+ B
+ C
+
+`;
+
+exports[`Radio fire change events when value changes 1`] = `
+
+ A
+ B
+ C
+
+`;
+
+exports[`Radio fire change events when value changes 2`] = `
+
+ A
+ B
+ C
+
+`;
diff --git a/components/radio/__tests__/__snapshots__/radio.test.js.snap b/components/radio/__tests__/__snapshots__/radio.test.js.snap
new file mode 100644
index 000000000..336db085f
--- /dev/null
+++ b/components/radio/__tests__/__snapshots__/radio.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Radio should render correctly 1`] = `Test `;
diff --git a/components/radio/__tests__/group.test.js b/components/radio/__tests__/group.test.js
new file mode 100644
index 000000000..f072321d0
--- /dev/null
+++ b/components/radio/__tests__/group.test.js
@@ -0,0 +1,148 @@
+import { mount } from '@vue/test-utils'
+import { asyncExpect } from '@/tests/utils'
+import Radio from '../radio'
+import RadioGroup from '../group'
+
+describe('Radio', () => {
+ function createRadioGroup (props, listeners = {}) {
+ return {
+ props: ['value'],
+ render () {
+ const groupProps = { ...props }
+ if (this.value !== undefined) {
+ groupProps.value = this.value
+ }
+ return (
+
+ A
+ B
+ C
+
+ )
+ },
+ }
+ }
+
+ function createRadioGroupByOption () {
+ const options = [
+ { label: 'A', value: 'A' },
+ { label: 'B', value: 'B' },
+ { label: 'C', value: 'C' },
+ ]
+ return {
+ render () {
+ return (
+
+ )
+ },
+ }
+ }
+
+ it('responses hover events', () => {
+ const onMouseEnter = jest.fn()
+ const onMouseLeave = jest.fn()
+
+ const wrapper = mount({
+ render () {
+ return (
+
+
+
+ )
+ },
+ })
+
+ wrapper.trigger('mouseenter')
+ expect(onMouseEnter).toHaveBeenCalled()
+
+ wrapper.trigger('mouseleave')
+ expect(onMouseLeave).toHaveBeenCalled()
+ })
+
+ it('fire change events when value changes', async () => {
+ const onChange = jest.fn()
+ const props = {}
+ const wrapper = mount(
+ createRadioGroup(props, {
+ change: onChange,
+ }),
+ { sync: false }
+ )
+ let radios = null
+ await asyncExpect(() => {
+ radios = wrapper.findAll('input')
+ // uncontrolled component
+ wrapper.vm.$refs.radioGroup.stateValue = 'B'
+ // wrapper.setData({ value: 'B' })
+ radios.at(0).trigger('change')
+ expect(onChange.mock.calls.length).toBe(1)
+ })
+ await asyncExpect(() => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ await asyncExpect(() => {
+ // controlled component
+ wrapper.setProps({ value: 'A' })
+ radios.at(1).trigger('change')
+ expect(onChange.mock.calls.length).toBe(2)
+ })
+ await asyncExpect(() => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+
+ // it('won\'t fire change events when value not changes', async () => {
+ // const onChange = jest.fn()
+
+ // const wrapper = mount(
+ // createRadioGroup({}, {
+ // change: onChange,
+ // }),
+ // { sync: false }
+ // )
+ // const radios = wrapper.findAll('input')
+ // await asyncExpect(() => {
+ // // uncontrolled component
+ // wrapper.vm.$refs.radioGroup.stateValue = 'B'
+ // radios.at(1).trigger('change')
+ // expect(onChange.mock.calls.length).toBe(0)
+ // })
+
+ // await asyncExpect(() => {
+
+ // }, 0)
+
+ // // // controlled component
+ // // wrapper.setProps({ value: 'A' })
+ // // radios.at(0).trigger('change')
+ // // expect(onChange.mock.calls.length).toBe(0)
+ // })
+
+ it('optional should correct render', () => {
+ const wrapper = mount(
+ createRadioGroupByOption()
+ )
+ const radios = wrapper.findAll('input')
+
+ expect(radios.length).toBe(3)
+ })
+
+ it('all children should have a name property', () => {
+ const GROUP_NAME = 'radiogroup'
+ const wrapper = mount(
+ createRadioGroup({ name: GROUP_NAME })
+ )
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.findAll('input[type="radio"]').wrappers.forEach((el) => {
+ expect(el.element.name).toEqual(GROUP_NAME)
+ }))
+ })
+})
diff --git a/components/radio/__tests__/radio.test.js b/components/radio/__tests__/radio.test.js
new file mode 100644
index 000000000..882540a00
--- /dev/null
+++ b/components/radio/__tests__/radio.test.js
@@ -0,0 +1,41 @@
+import { mount } from '@vue/test-utils'
+import { asyncExpect } from '@/tests/utils'
+import Radio from '../radio'
+import focusTest from '../../../tests/shared/focusTest'
+
+describe('Radio', () => {
+ focusTest(Radio)
+
+ it('should render correctly', () => {
+ const wrapper = mount({
+ render () {
+ return Test
+ },
+ })
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+
+ it('responses hover events', async () => {
+ const onMouseEnter = jest.fn()
+ const onMouseLeave = jest.fn()
+
+ const wrapper = mount({
+ render () {
+ return
+ },
+ }, { sync: false })
+ await asyncExpect(() => {
+ wrapper.trigger('mouseenter')
+ })
+ await asyncExpect(() => {
+ expect(onMouseEnter).toHaveBeenCalled()
+ })
+ wrapper.trigger('mouseleave')
+ await asyncExpect(() => {
+ expect(onMouseLeave).toHaveBeenCalled()
+ })
+ })
+})
diff --git a/components/tabs/__tests__/__snapshots__/index.test.js.snap b/components/tabs/__tests__/__snapshots__/index.test.js.snap
new file mode 100644
index 000000000..3e912fe91
--- /dev/null
+++ b/components/tabs/__tests__/__snapshots__/index.test.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Tabs tabPosition remove card 1`] = `
+
+`;
diff --git a/components/tabs/__tests__/index.test.js b/components/tabs/__tests__/index.test.js
new file mode 100644
index 000000000..0d2adf049
--- /dev/null
+++ b/components/tabs/__tests__/index.test.js
@@ -0,0 +1,50 @@
+import { mount } from '@vue/test-utils'
+import { asyncExpect } from '@/tests/utils'
+import Tabs from '..'
+
+const { TabPane } = Tabs
+
+describe('Tabs', () => {
+ describe('editable-card', () => {
+ let handleEdit
+ let wrapper
+
+ beforeEach(() => {
+ handleEdit = jest.fn()
+ wrapper = mount({
+ render () {
+ return (
+
+ foo
+
+ )
+ },
+ })
+ })
+
+ it('add card', () => {
+ wrapper.find('.ant-tabs-new-tab').trigger('click')
+ expect(handleEdit.mock.calls[0][1]).toBe('add')
+ })
+
+ it('remove card', () => {
+ wrapper.find('.anticon-close').trigger('click')
+ expect(handleEdit).toBeCalledWith('1', 'remove')
+ })
+ })
+
+ describe('tabPosition', () => {
+ it('remove card', () => {
+ const wrapper = mount({
+ render () {
+ return (
+
+ foo
+
+ )
+ },
+ })
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+})