Repository: IduxFE/components
Pull request #189 (2021-02-04 09:04:56)

test: add test uitls
## PR Checklist

Please check if your PR fulfills the following requirements:

- [x] The commit message follows [our guidelines](https://github.com/IduxFE/components/blob/main/docs/contributing.zh.md#commit)
- [x] Tests for the changes have been added/updated or not needed
- [x] Docs and demo have been added/updated or not needed

## PR Type

What kind of change does this PR introduce?

- [ ] Bugfix
- [ ] Feature
- [ ] Component style update
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Application (the showcase website) / infrastructure changes
- [x] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

## What is the new behavior?

## Other information


codecov[bot]: # [Codecov](https://codecov.io/gh/IduxFE/components/pull/189?src=pr&el=h1) Report
> Merging [#189](https://codecov.io/gh/IduxFE/components/pull/189?src=pr&el=desc) (aa15270) into [main](https://codecov.io/gh/IduxFE/components/commit/8e5002ae7599ba1b05d27270c74493ff1eb1e53d?el=desc) (8e5002a) will **increase** coverage by `0.06%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/IduxFE/components/pull/189/graphs/tree.svg?width=650&height=150&src=pr&token=PGAUXP06V3)](https://codecov.io/gh/IduxFE/components/pull/189?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             main     #189      +/-   ##
==========================================
+ Coverage   95.82%   95.88%   +0.06%     
==========================================
  Files          74       74              
  Lines        1557     1557              
  Branches      265      265              
==========================================
+ Hits         1492     1493       +1     
+ Misses         34       33       -1     
  Partials       31       31              
```


| [Impacted Files](https://codecov.io/gh/IduxFE/components/pull/189?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/components/image/src/Image.vue](https://codecov.io/gh/IduxFE/components/pull/189/diff?src=pr&el=tree#diff-cGFja2FnZXMvY29tcG9uZW50cy9pbWFnZS9zcmMvSW1hZ2UudnVl) | `96.96% <0.00%> (-3.04%)` | :arrow_down: |
| [packages/components/input/src/useAutoRows.ts](https://codecov.io/gh/IduxFE/components/pull/189/diff?src=pr&el=tree#diff-cGFja2FnZXMvY29tcG9uZW50cy9pbnB1dC9zcmMvdXNlQXV0b1Jvd3MudHM=) | `66.66% <0.00%> (+2.29%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/IduxFE/components/pull/189?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/IduxFE/components/pull/189?src=pr&el=footer). Last update [8e5002a...9a9f6bd](https://codecov.io/gh/IduxFE/components/pull/189?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/jest.config.js b/jest.config.js
index 69b23647..bac1c4d5 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,7 +1,7 @@
 module.exports = {
   // u can change this option to a more specific folder for test single component or util when dev
   // for example, ['<rootDir>/packages/components/button']
-  roots: ['<rootDir>'],
+  roots: ['<rootDir>/packages'],
 
   testEnvironment: 'jsdom',
   transform: {
@@ -26,6 +26,7 @@ module.exports = {
   },
   moduleNameMapper: {
     '^@idux(.*)$': '<rootDir>/packages$1',
+    '^@tests(.*)$': '<rootDir>/tests$1',
   },
   moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
   collectCoverage: true,
@@ -38,5 +39,6 @@ module.exports = {
       statements: 85,
     },
   },
+  coveragePathIgnorePatterns: ['/node_modules/', '<rootDir>/tests'],
   reporters: ['default', 'jest-junit'],
 }
diff --git a/scripts/gen/template.ts b/scripts/gen/template.ts
index 30f882d8..2e75eebe 100644
--- a/scripts/gen/template.ts
+++ b/scripts/gen/template.ts
@@ -54,6 +54,7 @@ export * from './src/types'
 export function getTestTemplate(compName: string): string {
   return `import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
+import { renderWork } from '@tests'
 import Ix${compName} from '../src/${compName}.vue'
 import { ${compName}Props } from '../src/types'
 
@@ -70,10 +71,7 @@ describe('${compName}.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = ${compName}Mount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(Ix${compName})
 })
 `
 }
diff --git a/tsconfig.json b/tsconfig.json
index 6d11a142..50c3c5b4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,7 +22,8 @@
     "allowSyntheticDefaultImports": true,
     "paths": {
       "@idux": ["./packages"],
-      "@idux/*": ["./packages/*"]
+      "@idux/*": ["./packages/*"],
+      "@tests": ["./tests"]
     }
   },
   "exclude": ["node_modules", "output"]


Test patch:
diff --git a/packages/components/badge/__tests__/badge.spec.ts b/packages/components/badge/__tests__/badge.spec.ts
index 35d15313..5c001e35 100644
--- a/packages/components/badge/__tests__/badge.spec.ts
+++ b/packages/components/badge/__tests__/badge.spec.ts
@@ -1,6 +1,7 @@
-import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
-import Badge from '../src/Badge.vue'
+import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
+import { renderWork } from '@tests'
+import IxBadge from '../src/Badge.vue'
 import { BadgeProps } from '../src/types'
 
 describe('Badge.vue', () => {
@@ -10,16 +11,13 @@ describe('Badge.vue', () => {
 
   beforeEach(() => {
     BadgeMount = (options = {}) => {
-      return mount<BadgeProps>(Badge, {
+      return mount<BadgeProps>(IxBadge, {
         ...options,
       })
     }
   })
 
-  test('render work', () => {
-    const wrapper = BadgeMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxBadge)
 
   // 基本功能测试
   test('count work', async () => {
diff --git a/packages/components/button/__tests__/button.spec.ts b/packages/components/button/__tests__/button.spec.ts
index 0e075efa..86da7da8 100644
--- a/packages/components/button/__tests__/button.spec.ts
+++ b/packages/components/button/__tests__/button.spec.ts
@@ -1,28 +1,26 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
-import Button from '../src/Button.vue'
+import IxButton from '../src/Button.vue'
 import { ButtonProps } from '../src/types'
 
 describe('Button.vue', () => {
-  let buttonMount: (
+  let ButtonMount: (
     options?: MountingOptions<Partial<ButtonProps>>,
   ) => VueWrapper<InstanceType<DefineComponent<ButtonProps>>>
 
   beforeEach(() => {
-    buttonMount = (options = {}) => {
-      return mount<ButtonProps>(Button, {
+    ButtonMount = (options = {}) => {
+      return mount<ButtonProps>(IxButton, {
         ...options,
       })
     }
   })
 
-  test('render work', () => {
-    const wrapper = buttonMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxButton)
 
   test('mode work', async () => {
-    const wrapper = buttonMount({ props: { mode: 'primary' } })
+    const wrapper = ButtonMount({ props: { mode: 'primary' } })
 
     expect(wrapper.classes()).toContain('ix-button-primary')
     expect(wrapper.element.tagName).toEqual('BUTTON')
@@ -39,7 +37,7 @@ describe('Button.vue', () => {
   })
 
   test('danger work', async () => {
-    const wrapper = buttonMount()
+    const wrapper = ButtonMount()
 
     expect(wrapper.classes()).not.toContain('ix-button-danger')
 
@@ -49,7 +47,7 @@ describe('Button.vue', () => {
   })
 
   test('ghost work', async () => {
-    const wrapper = buttonMount()
+    const wrapper = ButtonMount()
 
     expect(wrapper.classes()).not.toContain('ix-button-ghost')
 
@@ -59,7 +57,7 @@ describe('Button.vue', () => {
   })
 
   test('disabled work', async () => {
-    const wrapper = buttonMount({ props: { mode: 'primary' } })
+    const wrapper = ButtonMount({ props: { mode: 'primary' } })
 
     expect(wrapper.classes()).not.toContain('ix-button-disabled')
 
@@ -69,7 +67,7 @@ describe('Button.vue', () => {
   })
 
   test('loading work', async () => {
-    const wrapper = buttonMount({ props: { icon: 'up', loading: true } })
+    const wrapper = ButtonMount({ props: { icon: 'up', loading: true } })
 
     expect(wrapper.findAll('.ix-icon').length).toEqual(1)
     expect(wrapper.find('.ix-icon-loading').exists()).toBeTruthy()
@@ -77,7 +75,7 @@ describe('Button.vue', () => {
   })
 
   test('size work', async () => {
-    const wrapper = buttonMount({ props: { size: 'large' } })
+    const wrapper = ButtonMount({ props: { size: 'large' } })
 
     expect(wrapper.classes()).toContain('ix-button-large')
 
@@ -88,7 +86,7 @@ describe('Button.vue', () => {
   })
 
   test('shape work', async () => {
-    const wrapper = buttonMount({ props: { shape: 'circle' } })
+    const wrapper = ButtonMount({ props: { shape: 'circle' } })
 
     expect(wrapper.classes()).toContain('ix-button-circle')
 
@@ -99,7 +97,7 @@ describe('Button.vue', () => {
   })
 
   test('block work', async () => {
-    const wrapper = buttonMount()
+    const wrapper = ButtonMount()
 
     expect(wrapper.classes()).not.toContain('ix-button-block')
 
@@ -109,7 +107,7 @@ describe('Button.vue', () => {
   })
 
   test('icon work', async () => {
-    const wrapper = buttonMount({ props: { icon: 'up' } })
+    const wrapper = ButtonMount({ props: { icon: 'up' } })
 
     expect(wrapper.find('.ix-icon-up').exists()).toBeTruthy()
 
@@ -122,7 +120,7 @@ describe('Button.vue', () => {
 
   test('slot work', async () => {
     const text = 'Button'
-    const wrapper = buttonMount({
+    const wrapper = ButtonMount({
       slots: {
         default: text,
       },
diff --git a/packages/components/button/__tests__/buttonGroup.spec.ts b/packages/components/button/__tests__/buttonGroup.spec.ts
index 6f4154ee..e3a8f694 100644
--- a/packages/components/button/__tests__/buttonGroup.spec.ts
+++ b/packages/components/button/__tests__/buttonGroup.spec.ts
@@ -3,6 +3,7 @@ import { PropType } from 'vue'
 import ButtonGroup from '../src/ButtonGroup.vue'
 import Button from '../src/Button.vue'
 import { ButtonMode, ButtonSize, ButtonShape } from '../src/types'
+import { renderWork } from '@tests'
 
 const TestComponent = {
   components: { ButtonGroup, Button },
@@ -22,12 +23,7 @@ const TestComponent = {
 }
 
 describe('ButtonGroup.vue', () => {
-  test('render work', () => {
-    const wrapper = mount(TestComponent)
-    expect(wrapper.classes()).toContain('ix-button-group')
-    expect(wrapper.findAll('.ix-button').length).toBe(4)
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(TestComponent)
 
   test('mode work', async () => {
     const wrapper = mount(TestComponent)
diff --git a/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap b/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap
index 8e5e1118..4c46f968 100644
--- a/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap
+++ b/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap
@@ -1,3 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Checkbox.vue render work 1`] = `"<label class=\\"ix-checkbox\\" role=\\"checkbox\\" ariachecked=\\"false\\" ariadisabled=\\"false\\"><span class=\\"ix-checkbox-input-wrapper\\"><input type=\\"checkbox\\" class=\\"ix-checkbox-input\\" truevalue=\\"true\\" falsevalue=\\"false\\" value=\\"\\"><span class=\\"ix-checkbox-inner\\"></span></span><span class=\\"ix-checkbox-label\\">option</span></label>"`;
+exports[`Checkbox.vue render work 1`] = `"<label class=\\"ix-checkbox\\" role=\\"checkbox\\" ariachecked=\\"false\\" ariadisabled=\\"false\\"><span class=\\"ix-checkbox-input-wrapper\\"><input type=\\"checkbox\\" class=\\"ix-checkbox-input\\" truevalue=\\"true\\" falsevalue=\\"false\\" value=\\"\\"><span class=\\"ix-checkbox-inner\\"></span></span><!--v-if--></label>"`;
diff --git a/packages/components/checkbox/__tests__/checkbox.spec.ts b/packages/components/checkbox/__tests__/checkbox.spec.ts
index d9d1cbe5..9c59d392 100644
--- a/packages/components/checkbox/__tests__/checkbox.spec.ts
+++ b/packages/components/checkbox/__tests__/checkbox.spec.ts
@@ -1,19 +1,10 @@
+import { renderWork } from '@tests'
 import { mount } from '@vue/test-utils'
 import { ref, nextTick } from 'vue'
 import IxCheckbox from '../src/Checkbox.vue'
 
 describe('Checkbox.vue', () => {
-  test('render work', () => {
-    const checked = ref(false)
-    const wrapper = mount({
-      components: { IxCheckbox },
-      template: `<ix-checkbox v-model:checked="checked">option</ix-checkbox>`,
-      setup() {
-        return { checked }
-      },
-    })
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxCheckbox)
 
   test('checked(v-model) work', async () => {
     const checked = ref(false)
diff --git a/packages/components/checkbox/__tests__/checkboxGroup.spec.ts b/packages/components/checkbox/__tests__/checkboxGroup.spec.ts
index 1334c28d..bb7be6c8 100644
--- a/packages/components/checkbox/__tests__/checkboxGroup.spec.ts
+++ b/packages/components/checkbox/__tests__/checkboxGroup.spec.ts
@@ -2,22 +2,21 @@ import { mount } from '@vue/test-utils'
 import CheckboxGroup from '../src/CheckboxGroup.vue'
 import Checkbox from '../src/Checkbox.vue'
 import { nextTick, Ref, ref } from 'vue'
+import { renderWork } from '@tests'
+
+const TestComponent = {
+  components: { CheckboxGroup, Checkbox },
+  template: `
+  <checkbox-group>
+    <checkbox value="option1"> option1 </checkbox>
+    <checkbox value="option2"> option2 </checkbox>
+    <checkbox value="option3"> option3 </checkbox>
+  </checkbox-group>
+  `,
+}
 
 describe('CheckboxGroup.vue and Checkbox.vue', () => {
-  test('render work', () => {
-    const wrapper = mount({
-      components: { CheckboxGroup, Checkbox },
-      template: `
-      <checkbox-group>
-        <checkbox value="option1"> option1 </checkbox>
-        <checkbox value="option2"> option2 </checkbox>
-        <checkbox value="option3"> option3 </checkbox>
-      </checkbox-group>
-      `,
-    })
-    expect(wrapper.findAll('.ix-checkbox').length).toBe(3)
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(TestComponent)
 
   test('value(v-model) work', async () => {
     const value = ref([]) as Ref<string[]>
diff --git a/packages/components/divider/__tests__/divider.spec.ts b/packages/components/divider/__tests__/divider.spec.ts
index 1f4dfe8b..7ac1e49a 100644
--- a/packages/components/divider/__tests__/divider.spec.ts
+++ b/packages/components/divider/__tests__/divider.spec.ts
@@ -1,3 +1,4 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
 import IxDivider from '../src/Divider.vue'
@@ -16,10 +17,7 @@ describe('Divider.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = DividerMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxDivider)
 
   test('type work', async () => {
     const wrapper = DividerMount()
diff --git a/packages/components/empty/__tests__/empty.spec.ts b/packages/components/empty/__tests__/empty.spec.ts
index a5dc4844..4111eb46 100644
--- a/packages/components/empty/__tests__/empty.spec.ts
+++ b/packages/components/empty/__tests__/empty.spec.ts
@@ -1,3 +1,4 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
 import IxEmpty from '../src/Empty.vue'
@@ -16,10 +17,7 @@ describe('Empty.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = EmptyMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxEmpty)
 
   test('image work', async () => {
     const wrapper = EmptyMount()
diff --git a/packages/components/icon/__tests__/icon.spec.ts b/packages/components/icon/__tests__/icon.spec.ts
index d538074f..8c0ca471 100644
--- a/packages/components/icon/__tests__/icon.spec.ts
+++ b/packages/components/icon/__tests__/icon.spec.ts
@@ -1,8 +1,9 @@
 import { useGlobalConfig } from '@idux/components/core/config'
+
 import { flushPromises, mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
 import { addIconDefinitions, fetchFromIconfont } from '../src/helper'
-import Icon from '../src/Icon.vue'
+import IxIcon from '../src/Icon.vue'
 import { IconProps, IconDefinition } from '../src/types'
 
 const Up: IconDefinition = { name: 'up', svgString: '<svg></svg>' }
@@ -14,7 +15,7 @@ describe('Icon.vue', () => {
 
   beforeEach(() => {
     IconMount = (options = {}) => {
-      return mount<IconProps>(Icon, {
+      return mount<IconProps>(IxIcon, {
         ...options,
       })
     }
@@ -44,8 +45,8 @@ describe('Icon.vue', () => {
     const loadIconDynamically = async (iconName: string) => `<svg data-icon="${iconName}"></svg>`
 
     const wrapper = mount({
-      components: { Icon },
-      template: `<Icon name="dynamic-up" />`,
+      components: { IxIcon },
+      template: `<IxIcon name="dynamic-up" />`,
       setup() {
         useGlobalConfig('icon', { loadIconDynamically })
       },
@@ -106,8 +107,8 @@ describe('Icon.vue', () => {
 
   test('tag work', async () => {
     const wrapper = mount({
-      components: { Icon },
-      template: `<Icon @click="onClick"/>`,
+      components: { IxIcon },
+      template: `<IxIcon @click="onClick"/>`,
       props: {
         onClick: { type: Function, default: () => void 0 },
       },
diff --git a/packages/components/image/__tests__/image.spec.ts b/packages/components/image/__tests__/image.spec.ts
index 9046d2f4..4840c3f1 100644
--- a/packages/components/image/__tests__/image.spec.ts
+++ b/packages/components/image/__tests__/image.spec.ts
@@ -1,6 +1,7 @@
-import { flushPromises, mount, MountingOptions, VueWrapper } from '@vue/test-utils'
+import { renderWork } from '@tests'
+import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
-import Image from '../src/Image.vue'
+import IxImage from '../src/Image.vue'
 import { ImageProps } from '../src/types'
 
 describe('Image.vue', () => {
@@ -10,25 +11,20 @@ describe('Image.vue', () => {
 
   beforeEach(() => {
     ImageMount = (options = {}) => {
-      return mount<ImageProps>(Image, {
+      return mount<ImageProps>(IxImage, {
         ...options,
       })
     }
   })
 
-  test('render work', async () => {
-    const wrapper = ImageMount()
-    await flushPromises()
-    await wrapper.find('img').trigger('load')
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxImage)
+
   test('render src work', async () => {
     const wrapper = ImageMount({
       props: {
         src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg',
       },
     })
-    await flushPromises()
     expect(wrapper.html()).toMatchSnapshot()
     await wrapper.setProps({ src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/vue.png' })
   })
@@ -39,7 +35,6 @@ describe('Image.vue', () => {
         src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg',
       },
     })
-    await flushPromises()
     expect(wrapper.html()).toMatchSnapshot()
     await wrapper.setProps({ width: '200px' })
     expect(wrapper.find('img').attributes()['style']).toMatch('width: 200px')
@@ -54,7 +49,7 @@ describe('Image.vue', () => {
         src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg',
       },
     })
-    await flushPromises()
+
     expect(wrapper.html()).toMatchSnapshot()
     await wrapper.setProps({ height: '200px' })
     expect(wrapper.find('img').attributes()['style']).toMatch('height: 200px')
@@ -68,7 +63,7 @@ describe('Image.vue', () => {
         src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg',
       },
     })
-    await flushPromises()
+
     expect(wrapper.html()).toMatchSnapshot()
     expect(wrapper.find('img').attributes()['alt']).toEqual('demo')
   })
@@ -78,7 +73,7 @@ describe('Image.vue', () => {
         src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg',
       },
     })
-    await flushPromises()
+
     expect(wrapper.html()).toMatchSnapshot()
     await wrapper.setProps({ objectFit: 'fill' })
     expect(wrapper.find('img').attributes()['style']).toMatch('fill')
@@ -93,7 +88,7 @@ describe('Image.vue', () => {
         src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg',
       },
     })
-    await flushPromises()
+
     expect(wrapper.html()).toMatchSnapshot()
     await wrapper.find('.ix-image-preview-is').trigger('click')
 
@@ -138,7 +133,7 @@ describe('Image.vue', () => {
       },
     })
     await wrapper.find('img').trigger('error')
-    await flushPromises()
+
     expect(wrapper.find('.ix-image-error').isVisible()).toBe(true)
   })
 })
diff --git a/packages/components/input/__tests__/input.spec.ts b/packages/components/input/__tests__/input.spec.ts
index d68ac384..db2b3c78 100644
--- a/packages/components/input/__tests__/input.spec.ts
+++ b/packages/components/input/__tests__/input.spec.ts
@@ -1,3 +1,4 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent, nextTick, ref } from 'vue'
 import IxInput from '../src/Input.vue'
@@ -16,10 +17,7 @@ describe('Input.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = InputMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxInput)
 
   test('v-model work', async () => {
     const valueRef = ref('init value')
diff --git a/packages/components/input/__tests__/textarea.spec.ts b/packages/components/input/__tests__/textarea.spec.ts
index c2111515..26e4569b 100644
--- a/packages/components/input/__tests__/textarea.spec.ts
+++ b/packages/components/input/__tests__/textarea.spec.ts
@@ -1,3 +1,4 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent, nextTick, ref } from 'vue'
 import IxTextarea from '../src/Textarea.vue'
@@ -16,10 +17,7 @@ describe('Textarea.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = TextareaMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxTextarea)
 
   test('v-model work', async () => {
     const valueRef = ref('init value')
diff --git a/packages/components/rate/__tests__/__snapshots__/rate.spec.ts.snap b/packages/components/rate/__tests__/__snapshots__/rate.spec.ts.snap
index ddcebf69..86affe07 100644
--- a/packages/components/rate/__tests__/__snapshots__/rate.spec.ts.snap
+++ b/packages/components/rate/__tests__/__snapshots__/rate.spec.ts.snap
@@ -1,3 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Rate.vue component render 1`] = `"<div class=\\"ix-rate-wrap\\"><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-highlight\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-highlight\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-highlight\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-normal\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-normal\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div></div>"`;
+exports[`Rate.vue render work 1`] = `"<div class=\\"ix-rate-wrap\\"><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-highlight\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-highlight\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-highlight\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-normal\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div><div class=\\"ix-rate-item\\" style=\\"cursor: pointer;\\"><div class=\\"ix-rate-full\\"><i class=\\"ix-icon ix-icon-star ix-rate-iconfont-main ix-rate-normal\\" role=\\"img\\" arialabel=\\"star\\"></i></div><!--v-if--></div></div>"`;
diff --git a/packages/components/rate/__tests__/rate.spec.ts b/packages/components/rate/__tests__/rate.spec.ts
index 3da30345..f2873606 100644
--- a/packages/components/rate/__tests__/rate.spec.ts
+++ b/packages/components/rate/__tests__/rate.spec.ts
@@ -1,7 +1,8 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
 
-import Rate from '../src/Rate.vue'
+import IxRate from '../src/Rate.vue'
 
 import { RateProps } from '../src/types'
 
@@ -11,7 +12,7 @@ describe('Rate.vue', () => {
 
   beforeEach(() => {
     RateMount = (options = {}) => {
-      return mount<RateProps>(Rate, {
+      return mount<RateProps>(IxRate, {
         ...options,
       })
     }
@@ -25,15 +26,7 @@ describe('Rate.vue', () => {
     }
   })
 
-  test('component render', () => {
-    const wrapper = RateMount({
-      props: {
-        value: 3,
-      },
-    })
-
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxRate, { props: { value: 3 } })
 
   test('init hightlight as value', () => {
     const wrapper = RateMount({
@@ -88,13 +81,13 @@ describe('Rate.vue', () => {
 
   test('touch half and allow half', async () => {
     const wrapper = mount({
-      template: `<Rate v-model:value="value" allow-half ref="testRateRef" />`,
+      template: `<IxRate v-model:value="value" allow-half ref="testRateRef" />`,
       data() {
         return {
           value: 4,
         }
       },
-      components: { Rate },
+      components: { IxRate },
     })
 
     const vm = wrapper.vm
@@ -109,13 +102,13 @@ describe('Rate.vue', () => {
 
   test('disable', async () => {
     const wrapper = mount({
-      template: `<Rate v-model:value="value" disabled ref="testRateRef" />`,
+      template: `<IxRate v-model:value="value" disabled ref="testRateRef" />`,
       data() {
         return {
           value: 4,
         }
       },
-      components: { Rate },
+      components: { IxRate },
     })
 
     const vm = wrapper.vm
@@ -130,13 +123,13 @@ describe('Rate.vue', () => {
 
   test('allow clear', async () => {
     const wrapper = mount({
-      template: `<Rate v-model:value="value" allow-clear ref="testRateRef" />`,
+      template: `<IxRate v-model:value="value" allow-clear ref="testRateRef" />`,
       data() {
         return {
           value: 2,
         }
       },
-      components: { Rate },
+      components: { IxRate },
     })
 
     await wrapper.setProps({ value: 3 })
diff --git a/packages/components/result/__tests__/result.spec.ts b/packages/components/result/__tests__/result.spec.ts
index 6ad152c9..6467f492 100644
--- a/packages/components/result/__tests__/result.spec.ts
+++ b/packages/components/result/__tests__/result.spec.ts
@@ -3,6 +3,7 @@ import { defineComponent, DefineComponent } from 'vue'
 import IxResult from '../src/Result.vue'
 import { ResultProps } from '../src/types'
 import { IxIcon } from '../../icon'
+import { renderWork } from '@tests'
 
 describe('Result.vue', () => {
   let ResultMount: (
@@ -17,10 +18,7 @@ describe('Result.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = ResultMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxResult)
 
   test('status work', async () => {
     const wrapper = ResultMount()
diff --git a/packages/components/space/__tests__/space.spec.ts b/packages/components/space/__tests__/space.spec.ts
index f106804c..910e9225 100644
--- a/packages/components/space/__tests__/space.spec.ts
+++ b/packages/components/space/__tests__/space.spec.ts
@@ -5,6 +5,7 @@ import { IxButton } from '../../button'
 import { IxDivider } from '../../divider'
 import { SpaceAlign, SpaceDirection, SpaceSize } from '../src/types'
 import { isNil } from '@idux/cdk/utils'
+import { renderWork } from '@tests'
 
 const TestComponent = defineComponent({
   components: { IxSpace, IxButton, IxDivider },
@@ -41,11 +42,7 @@ const TestComponent = defineComponent({
 })
 
 describe('Space.vue', () => {
-  test('render work', () => {
-    const wrapper = mount(TestComponent)
-    expect(wrapper.classes()).toContain('ix-space')
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(TestComponent)
 
   test('align work', async () => {
     const wrapper = mount(TestComponent)
diff --git a/packages/components/spin/__tests__/spin.spec.ts b/packages/components/spin/__tests__/spin.spec.ts
index 283e0479..693ef015 100644
--- a/packages/components/spin/__tests__/spin.spec.ts
+++ b/packages/components/spin/__tests__/spin.spec.ts
@@ -1,3 +1,4 @@
+import { renderWork } from '@tests'
 import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
 import { DefineComponent } from 'vue'
 import IxSpin from '../src/Spin.vue'
@@ -14,10 +15,7 @@ describe('Spin.vue', () => {
     }
   })
 
-  test('render work', () => {
-    const wrapper = SpinMount()
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+  renderWork(IxSpin)
 
   test('spinning work', async () => {
     const wrapper = SpinMount({ props: { spinning: false } })
diff --git a/packages/components/typography/__tests__/typography.spec.ts b/packages/components/typography/__tests__/typography.spec.ts
index 0d24b60a..d53f9664 100644
--- a/packages/components/typography/__tests__/typography.spec.ts
+++ b/packages/components/typography/__tests__/typography.spec.ts
@@ -1,7 +1,29 @@
+import { renderWork } from '@tests'
 import { mount, VueWrapper } from '@vue/test-utils'
 import { ComponentOptions } from 'vue'
 import IxTypography from '../src/typography'
 
+const TestComponent = {
+  template: `
+  <article v-typography>
+    <h1 v-typography>Title</h1>
+    <p v-typography>
+      In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.
+    </p>
+    <div v-typography>
+      <ul>
+        <li><a href="/docs/spec/proximity">Principles</a></li>
+        <li><a href="/docs/pattern/navigation">Patterns</a></li>
+        <li><a href="/docs/resource/download">Resource Download</a></li>
+      </ul>
+    </div>
+    <p v-typography>
+      Press <span v-typography><kbd>Esc</kbd></span> to exist...
+    </p>
+  </article>
+  `,
+}
+
 describe('typography.ts', () => {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   let typographyMount: (options: ComponentOptions) => VueWrapper<any>
@@ -11,30 +33,8 @@ describe('typography.ts', () => {
       return mount(options, { global: { directives: { typography: IxTypography } } })
     }
   })
-  test('render work', () => {
-    const TestComponent = {
-      template: `
-      <article v-typography>
-        <h1 v-typography>Title</h1>
-        <p v-typography>
-          In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.
-        </p>
-        <div v-typography>
-          <ul>
-            <li><a href="/docs/spec/proximity">Principles</a></li>
-            <li><a href="/docs/pattern/navigation">Patterns</a></li>
-            <li><a href="/docs/resource/download">Resource Download</a></li>
-          </ul>
-        </div>
-        <p v-typography>
-          Press <span v-typography><kbd>Esc</kbd></span> to exist...
-        </p>
-      </article>
-      `,
-    }
-    const wrapper = typographyMount(TestComponent)
-    expect(wrapper.html()).toMatchSnapshot()
-  })
+
+  renderWork(TestComponent, { global: { directives: { typography: IxTypography } } })
 
   test('type work', async () => {
     const TestComponent = {
diff --git a/tests/index.ts b/tests/index.ts
new file mode 100644
index 00000000..9c56149e
--- /dev/null
+++ b/tests/index.ts
@@ -0,0 +1,1 @@
+export * from './utils'
diff --git a/tests/utils.ts b/tests/utils.ts
new file mode 100644
index 00000000..2cb2f256
--- /dev/null
+++ b/tests/utils.ts
@@ -0,0 +1,37 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import { mount } from '@vue/test-utils'
+
+export const wait = (timeout?: number): Promise<unknown> => {
+  return new Promise(resolve => setTimeout(resolve, timeout))
+}
+
+export const waitRAF = (): Promise<unknown> => {
+  return new Promise(resolve => requestAnimationFrame(resolve))
+}
+
+export const resize = (width = window.innerWidth, height = window.innerHeight): Promise<unknown> => {
+  ;(window as any).innerWidth = width
+  ;(window as any).innerHeight = height
+  window.dispatchEvent(new Event('resize'))
+  return wait(100)
+}
+
+export const scroll = (y: number): Promise<unknown> => {
+  ;(window as any).pageYOffset = y
+  window.dispatchEvent(new Event('scroll'))
+  return wait(100)
+}
+
+export const renderWork = (component: any, options = {}): void => {
+  test('render work', () => {
+    const wrapper = mount(component, { ...options })
+    expect(wrapper.html()).toMatchSnapshot()
+
+    expect(() => {
+      wrapper.vm.$forceUpdate()
+      wrapper.unmount()
+    }).not.toThrow()
+  })
+}