Repository: Tencent/tdesign-mobile-vue
Pull request #374 (2022-09-28 14:19:49)

fix(DropdownMenu): fix the onChange event is invalid
<!--
首先，感谢你的贡献！😄
请阅读并遵循 [TDesign 贡献指南](https://github.com/Tencent/tdesign/blob/main/docs/contributing.md)，填写以下 pull request 的信息。
PR 在维护者审核通过后会合并，谢谢！
-->

### 🤔 这个 PR 的性质是？

- [x] 日常 bug 修复
- [x] 测试用例


### 📝 更新日志

<!--
从用户角度描述具体变化，以及可能的 breaking change 和其他风险。
-->

- fix(DropdownMenu): 修复 `onChange` 事件无效的问题
- feat(DropdownMenu): 新增单元测试



github-actions[bot]: <a href="https://preview-pr374-tdesign-mobile-vue.surge.sh"><img height="96" alt="完成" src="https://user-images.githubusercontent.com/15634204/150816437-9f5bb788-cd67-4cbc-9897-b82d74e9aa65.png" /></a>
<!-- [工作流地址](https://github.com/Tencent/tdesign-mobile-vue/actions/runs/3144372908) -->
<!-- AUTO_PREVIEW_HOOK -->
github-actions[bot]: **Size Change:** +6 B (0%) 

**Total Size:** 255 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 130 kB | -18 B (0%) |
| `./dist/tdesign.min.js` | 79.1 kB | +24 B (0%) |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 24 kB |
| `./dist/tdesign.min.css` | 22.2 kB |

</details>


github-actions[bot]: **Size Change:** +6 B (0%) 

**Total Size:** 255 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 130 kB | -18 B (0%) |
| `./dist/tdesign.min.js` | 79.1 kB | +24 B (0%) |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 24 kB |
| `./dist/tdesign.min.css` | 22.2 kB |

</details>



Patch:
diff --git a/src/divider/demos/align.vue b/src/divider/demos/align.vue
index 9a342bd3..af68bd40 100644
--- a/src/divider/demos/align.vue
+++ b/src/divider/demos/align.vue
@@ -1,12 +1,19 @@
 <template>
-  <div class="demo-6">
-    <span>正直</span>
-    <t-divider align="left">TDesign</t-divider>
-    <span>进取</span>
-    <t-divider>TDesign</t-divider>
-    <span>合作</span>
-    <t-divider align="right">TDesign</t-divider>
-    <span>创新</span>
+  <div>
+    <h3>文字+直线</h3>
+    <t-divider class="demo-5"> 文字信息 </t-divider>
+
+    <h3>纯文字</h3>
+    <t-divider line-color="transparent"> 没有更多了~ </t-divider>
   </div>
 </template>
-<script lang="ts" setup></script>
+
+<style lang="less" scoped>
+h3 {
+  font-size: 12px;
+  font-weight: 400;
+  font-family: 'PingFang SC';
+  color: #a9a9a9;
+  padding: 16px;
+}
+</style>
diff --git a/src/divider/demos/base.vue b/src/divider/demos/base.vue
index 8467d814..6c22dfd8 100644
--- a/src/divider/demos/base.vue
+++ b/src/divider/demos/base.vue
@@ -1,8 +1,31 @@
 <template>
-  <div class="demo-6">
+  <div>
     <h3>直线拉通</h3>
     <t-divider></t-divider>
-    <h3>直线拉通</h3>
+
+    <h3>虚线拉通</h3>
+    <t-divider dashed></t-divider>
   </div>
 </template>
-<script lang="ts" setup></script>
+
+<style lang="less" scoped>
+h3 {
+  font-size: 12px;
+  font-weight: 400;
+  font-family: 'PingFang SC';
+  color: #a9a9a9;
+  padding: 16px;
+}
+
+.demo-2 {
+  margin: 0 16px;
+}
+
+.demo-3 {
+  margin-left: 16px;
+}
+
+.demo-4 {
+  margin-left: 76px;
+}
+</style>
diff --git a/src/divider/demos/horizontal.vue b/src/divider/demos/horizontal.vue
index 0d51bdbd..b7581fe0 100644
--- a/src/divider/demos/horizontal.vue
+++ b/src/divider/demos/horizontal.vue
@@ -1,12 +1,34 @@
 <template>
-  <div class="demo-6">
-    <span>正直</span>
-    <t-divider></t-divider>
-    <span>进取</span>
-    <t-divider></t-divider>
-    <span>合作</span>
-    <t-divider></t-divider>
-    <span>创新</span>
+  <div>
+    <h3>左右间距</h3>
+    <t-divider class="demo-2"></t-divider>
+
+    <h3>右侧拉通</h3>
+    <t-divider class="demo-3"></t-divider>
+
+    <h3>自定义左侧间距</h3>
+    <t-divider class="demo-4"></t-divider>
   </div>
 </template>
-<script lang="ts" setup></script>
+
+<style lang="less" scoped>
+h3 {
+  font-size: 12px;
+  font-weight: 400;
+  font-family: 'PingFang SC';
+  color: #a9a9a9;
+  padding: 16px;
+}
+
+.demo-2 {
+  margin: 0 16px;
+}
+
+.demo-3 {
+  margin-left: 16px;
+}
+
+.demo-4 {
+  margin-left: 76px;
+}
+</style>
diff --git a/src/divider/demos/mobile.vue b/src/divider/demos/mobile.vue
index c0c012d0..d98b5a52 100644
--- a/src/divider/demos/mobile.vue
+++ b/src/divider/demos/mobile.vue
@@ -6,74 +6,17 @@
     </p>
     <tdesign-demo-block title="01 类型" summary="分割符主要是由直线和文字组成">
       <div style="background: #fff">
-        <h3>直线拉通</h3>
-        <t-divider></t-divider>
-
-        <h3>虚线拉通</h3>
-        <t-divider dashed></t-divider>
-
-        <h3>左右间距</h3>
-        <t-divider class="demo-2"></t-divider>
-
-        <h3>右侧拉通</h3>
-        <t-divider class="demo-3"></t-divider>
-
-        <h3>自定义左侧间距</h3>
-        <t-divider class="demo-4"></t-divider>
-
-        <h3>文字+直线</h3>
-        <t-divider class="demo-5"> 文字信息 </t-divider>
-
-        <h3>纯文字</h3>
-        <t-divider line-color="transparent"> 没有更多了~ </t-divider>
-
-        <h3>垂直分割</h3>
-        <div class="demo-6">
-          <span>文字信息</span>
-          <t-divider layout="vertical"></t-divider>
-          <span>文字信息</span>
-          <t-divider layout="vertical"></t-divider>
-          <span>文字信息</span>
-        </div>
+        <baseDemo />
+        <horizontalDemo />
+        <alignDemo />
+        <verticalDemo />
       </div>
     </tdesign-demo-block>
   </div>
 </template>
-<script lang="ts" setup></script>
-<style lang="less" scoped>
-h3 {
-  font-size: 12px;
-  font-weight: 400;
-  font-family: 'PingFang SC';
-  color: #a9a9a9;
-  padding: 16px;
-}
-
-.demo-2 {
-  margin: 0 16px;
-}
-
-.demo-3 {
-  margin-left: 16px;
-}
-
-.demo-4 {
-  margin-left: 76px;
-}
-
-.demo-5 {
-  margin: 0 16px;
-}
-
-.demo-6 {
-  display: flex;
-  height: 20px;
-  line-height: 20px;
-  align-items: center;
-  margin: 0 16px;
-  font-size: 12px;
-  .t-divider {
-    height: 12px;
-  }
-}
-</style>
+<script lang="ts" setup>
+import baseDemo from './base.vue';
+import alignDemo from './align.vue';
+import horizontalDemo from './horizontal.vue';
+import verticalDemo from './vertical.vue';
+</script>
diff --git a/src/divider/demos/vertical.vue b/src/divider/demos/vertical.vue
index e432df11..f5ddb31b 100644
--- a/src/divider/demos/vertical.vue
+++ b/src/divider/demos/vertical.vue
@@ -1,16 +1,25 @@
 <template>
-  <div class="demo-6">
-    <span>正直信息</span>
-    <t-divider layout="vertical"></t-divider>
-    <span>进取</span>
-    <t-divider layout="vertical"></t-divider>
-    <span>合作</span>
-    <t-divider layout="vertical"></t-divider>
-    <span>创新</span>
+  <div>
+    <h3>垂直分割</h3>
+    <div class="demo-6">
+      <span>文字信息</span>
+      <t-divider layout="vertical"></t-divider>
+      <span>文字信息</span>
+      <t-divider layout="vertical"></t-divider>
+      <span>文字信息</span>
+    </div>
   </div>
 </template>
-<script lang="ts" setup></script>
+
 <style lang="less" scoped>
+h3 {
+  font-size: 12px;
+  font-weight: 400;
+  font-family: 'PingFang SC';
+  color: #a9a9a9;
+  padding: 16px;
+}
+
 .demo-6 {
   display: flex;
   height: 20px;
diff --git a/src/dropdown-menu/dropdown-item.vue b/src/dropdown-menu/dropdown-item.vue
index 7d7bb479..5b971dbd 100644
--- a/src/dropdown-menu/dropdown-item.vue
+++ b/src/dropdown-menu/dropdown-item.vue
@@ -128,7 +128,7 @@ import TButton from '../button';
 import TCheckbox, { CheckboxGroup as TCheckboxGroup } from '../checkbox';
 import TRadioGroup from '../radio-group';
 import TransAniControl from './trans-ani-control';
-import { useDefault, useEmitEvent } from '../shared';
+import { useVModel, useEmitEvent } from '../shared';
 import DropdownItemProps from './dropdown-item-props';
 import { DropdownMenuState, DropdownMenuControl } from './context';
 import { TdDropdownMenuProps, TdDropdownItemProps, TdDropdownItemOption, TdDropdownItemOptionValueType } from './type';
@@ -160,11 +160,8 @@ export default defineComponent({
   setup(props, context: SetupContext) {
     const emitEvent = useEmitEvent(props, context.emit);
     // 受控 value 属性
-    const [passInValue, setValue] = useDefault<
-      TdDropdownItemOptionValueType | Array<TdDropdownItemOptionValueType>,
-      TdDropdownItemProps
-    >(props, context.emit, 'value', 'change');
-
+    const { value, modelValue } = toRefs(props);
+    const [passInValue, setValue] = useVModel(value, modelValue, props.defaultValue, props.onChange);
     // 从父组件取属性、状态和控制函数
     const menuProps = inject('dropdownMenuProps') as TdDropdownMenuProps;
     const menuState = inject('dropdownMenuState') as DropdownMenuState;
@@ -367,13 +364,15 @@ export default defineComponent({
       }
     };
     // 初始值更新一次选中项
-    updateSelectValue(passInValue.value);
+    updateSelectValue(passInValue.value || null);
     // 跟踪 modelValue 更新选项
     watch(
       () => passInValue.value,
-      (val) => updateSelectValue(val),
+      (val) => {
+        if (!val) return;
+        updateSelectValue(val);
+      },
     );
-
     // 底部按键是否可用
     const isBtnDisabled = computed(() => {
       switch (props.optionsLayout) {
diff --git a/src/grid/demos/badge.vue b/src/grid/demos/badge.vue
index 93cdd08e..7d8e3a06 100644
--- a/src/grid/demos/badge.vue
+++ b/src/grid/demos/badge.vue
@@ -1,22 +1,23 @@
 <template>
-  <div>
+  <div class="grid-demo">
     <t-grid>
       <t-grid-item text="标题文字" :image="imgUrl" :badge-props="{ dot: true }" />
       <t-grid-item text="标题文字" :image="imgUrl" :badge-props="{ count: 3 }" />
       <t-grid-item text="标题五字内" :image="imgUrl" :badge-props="{ count: 'NEW' }" />
-      <t-grid-item text="标题五字内" :image="imgUrl" :badge-props="{ count: '...' }" />
+      <t-grid-item text="标题五字内" :image="imgUrl" :badge-props="{ count: '···' }" />
     </t-grid>
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  setup() {
-    return {
-      imgUrl: 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png',
-    };
-  },
-});
+<script lang="ts" setup>
+const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png';
 </script>
+<style scoped lang="less">
+.grid-demo {
+  margin: 0 16px 8px;
+
+  &.short-width {
+    margin: 0 26px 8px;
+  }
+}
+</style>
diff --git a/src/grid/demos/base.vue b/src/grid/demos/base.vue
index 0865a0db..f0deada1 100644
--- a/src/grid/demos/base.vue
+++ b/src/grid/demos/base.vue
@@ -4,7 +4,7 @@
       <t-grid-item text="标题文字" :image="imgUrl" />
       <t-grid-item text="标题文字" :image="imgUrl" />
       <t-grid-item text="标题文字" :image="imgUrl" />
-      <t-grid-item text="最多四字" :image="imgUrl" />
+      <t-grid-item text="标题五字内" :image="imgUrl" />
     </t-grid>
   </div>
   <div class="grid-demo">
@@ -34,16 +34,8 @@
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  setup() {
-    return {
-      imgUrl: 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png',
-    };
-  },
-});
+<script lang="ts" setup>
+const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png';
 </script>
 <style scoped lang="less">
 .grid-demo {
diff --git a/src/grid/demos/desc.vue b/src/grid/demos/desc.vue
index 258df487..33dc430d 100644
--- a/src/grid/demos/desc.vue
+++ b/src/grid/demos/desc.vue
@@ -1,21 +1,27 @@
 <template>
-  <div>
+  <div class="grid-demo">
     <t-grid :column="3">
       <t-grid-item text="标题文字" :image="imgUrl" description="说明文字" />
       <t-grid-item text="标题文字" :image="imgUrl" description="说明文字" />
       <t-grid-item text="标题最多六字" :image="imgUrl" description="说明最多六字" />
     </t-grid>
   </div>
+  <div class="grid-demo">
+    <t-grid :column="2">
+      <t-grid-item text="标题文字" layout="horizontal" :image="imgUrl" description="说明文字" />
+      <t-grid-item text="标题文字" layout="horizontal" :image="imgUrl" description="说明文字" />
+    </t-grid>
+  </div>
 </template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  setup() {
-    return {
-      imgUrl: 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png',
-    };
-  },
-});
+<script lang="ts" setup>
+const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png';
 </script>
+<style scoped lang="less">
+.grid-demo {
+  margin: 0 16px 8px;
+
+  &.short-width {
+    margin: 0 26px 8px;
+  }
+}
+</style>
diff --git a/src/grid/demos/mobile.vue b/src/grid/demos/mobile.vue
index efc28104..d2ac418c 100644
--- a/src/grid/demos/mobile.vue
+++ b/src/grid/demos/mobile.vue
@@ -3,85 +3,19 @@
     <h1 class="title">Grid 宫格</h1>
     <p class="summary">用于功能入口布局，将页面或特定区域切分成若干等大的区块，形成若干功能入口。</p>
     <tdesign-demo-block title="01 基础宫格" summary=" ">
-      <div class="grid-demo">
-        <t-grid>
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题五字内" :image="imgUrl" />
-        </t-grid>
-      </div>
-      <div class="grid-demo">
-        <t-grid :column="5">
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="最多四字" :image="imgUrl" />
-        </t-grid>
-      </div>
-      <div class="grid-demo">
-        <t-grid :column="3">
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题最多六字" :image="imgUrl" />
-        </t-grid>
-      </div>
-      <div class="grid-demo short-width">
-        <t-grid :column="5">
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-          <t-grid-item text="标题文字" :image="imgUrl" />
-        </t-grid>
-      </div>
+      <baseDemo />
     </tdesign-demo-block>
     <tdesign-demo-block title="02 带说明宫格" summary=" ">
-      <div class="grid-demo">
-        <t-grid :column="3">
-          <t-grid-item text="标题文字" :image="imgUrl" description="说明文字" />
-          <t-grid-item text="标题文字" :image="imgUrl" description="说明文字" />
-          <t-grid-item text="标题最多六字" :image="imgUrl" description="说明最多六字" />
-        </t-grid>
-      </div>
-      <div class="grid-demo">
-        <t-grid :column="2">
-          <t-grid-item text="标题文字" layout="horizontal" :image="imgUrl" description="说明文字" />
-          <t-grid-item text="标题文字" layout="horizontal" :image="imgUrl" description="说明文字" />
-        </t-grid>
-      </div>
+      <descDemo />
     </tdesign-demo-block>
     <tdesign-demo-block title="03 带徽标宫格" summary=" ">
-      <div class="grid-demo">
-        <t-grid>
-          <t-grid-item text="标题文字" :image="imgUrl" :badge-props="{ dot: true }" />
-          <t-grid-item text="标题文字" :image="imgUrl" :badge-props="{ count: 3 }" />
-          <t-grid-item text="标题五字内" :image="imgUrl" :badge-props="{ count: 'NEW' }" />
-          <t-grid-item text="标题五字内" :image="imgUrl" :badge-props="{ count: '···' }" />
-        </t-grid>
-      </div>
+      <badgeDemo />
     </tdesign-demo-block>
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  setup() {
-    return {
-      imgUrl: 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png',
-    };
-  },
-});
+<script lang="ts" setup>
+import baseDemo from './base.vue';
+import descDemo from './desc.vue';
+import badgeDemo from './badge.vue';
 </script>
-<style scoped lang="less">
-.grid-demo {
-  margin: 0 16px 8px;
-
-  &.short-width {
-    margin: 0 26px 8px;
-  }
-}
-</style>
diff --git a/src/grid/grid-item.vue b/src/grid/grid-item.vue
index a1214ffc..22c555f6 100644
--- a/src/grid/grid-item.vue
+++ b/src/grid/grid-item.vue
@@ -62,7 +62,7 @@ export default defineComponent({
       const borderStyle = {};
       if (border.value) {
         if (typeof border.value !== 'boolean') {
-          const { color, width, style } = border;
+          const { color, width, style } = border.value;
           return {
             borderColor: color,
             borderWidth: width,
@@ -74,8 +74,8 @@ export default defineComponent({
       const style = {
         flexBasis: percent,
         flexDirection: isHorz ? ('row' as const) : ('column' as const),
-        paddingLeft: gutter.value ? `${gutter}px` : 0,
-        paddingRight: gutter.value ? `${gutter}px` : 0,
+        paddingLeft: gutter.value ? `${gutter.value}px` : 0,
+        paddingRight: gutter.value ? `${gutter.value}px` : 0,
         alignItems: 'center',
         justifyContent: 'center',
         textAlign: ['center', 'left'].includes(align.value) ? align.value : 'center',


Test patch:
diff --git a/src/action-sheet/__test__/__snapshots__/demo.test.jsx.snap b/src/action-sheet/__test__/__snapshots__/demo.test.jsx.snap
index d2291c64..f5603950 100644
--- a/src/action-sheet/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/action-sheet/__test__/__snapshots__/demo.test.jsx.snap
@@ -72,8 +72,18 @@ exports[`ActionSheet > ActionSheet gridVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -83,7 +93,12 @@ exports[`ActionSheet > ActionSheet gridVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -494,8 +509,18 @@ exports[`ActionSheet > ActionSheet listVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -505,7 +530,12 @@ exports[`ActionSheet > ActionSheet listVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -587,8 +617,18 @@ exports[`ActionSheet > ActionSheet listVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -598,7 +638,12 @@ exports[`ActionSheet > ActionSheet listVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -671,8 +716,18 @@ exports[`ActionSheet > ActionSheet listVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -682,7 +737,12 @@ exports[`ActionSheet > ActionSheet listVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -842,8 +902,18 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -853,7 +923,12 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -936,8 +1011,18 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -947,7 +1032,12 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -1021,8 +1111,18 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1032,7 +1132,12 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -1276,8 +1381,18 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
   <div
     class="t-popup t-action-sheet"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1287,7 +1402,12 @@ exports[`ActionSheet > ActionSheet mobileVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
diff --git a/src/avatar/__test__/index.test.jsx b/src/avatar/__test__/index.test.jsx
new file mode 100644
index 00000000..0418a633
--- /dev/null
+++ b/src/avatar/__test__/index.test.jsx
@@ -0,0 +1,122 @@
+import { h, nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import { UserIcon } from 'tdesign-icons-vue-next';
+import Avatar from '../avatar.vue';
+import AvatarGroup from '../avatar-group.vue';
+import Badge from '../../badge/badge.vue';
+import { vi } from 'vitest';
+
+const IMAGE = 'https://tdesign.gtimg.com/site/avatar.jpg';
+const IMAGE_FAIL = 'https://tdesign.gtimg.com/mobile/demos/avatar_11112233.png';
+const userIconFunc = () => h(UserIcon);
+
+const shapeList = ['circle', 'round'];
+const sizeList = ['small', 'medium', 'large'];
+
+describe('Avatar.vue', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Avatar image={IMAGE} />);
+    expect(wrapper.classes()).toContain('t-avatar');
+    const img = wrapper.find('.t-avatar__inner > img');
+    expect(img.exists()).toBeTruthy();
+    expect(img.attributes('src')).toBe(IMAGE);
+  });
+
+  it('icon render', async () => {
+    const wrapper = mount(() => <Avatar icon={userIconFunc} />);
+    expect(wrapper.findComponent(UserIcon).exists()).toBeTruthy();
+  });
+
+  it('text render', async () => {
+    const wrapper = mount(() => <Avatar>A</Avatar>);
+    const span = wrapper.find('.t-avatar__inner > span');
+    expect(span.exists()).toBeTruthy();
+    expect(span.text()).toBe('A');
+  });
+
+  it('icon slot render', async () => {
+    const wrapper = mount(Avatar, {
+      slots: {
+        icon: userIconFunc,
+      },
+    });
+    const icon = wrapper.find('.t-avatar__inner > .t-avatar__icon');
+    expect(icon.exists()).toBeTruthy();
+    expect(wrapper.findComponent(UserIcon).exists()).toBeTruthy();
+  });
+
+  it('shape render', async () => {
+    shapeList.forEach((s) => {
+      const wrapper = mount(() => <Avatar image={IMAGE} shape={s}></Avatar>);
+      expect(wrapper.classes()).toContain(`t-avatar--${s}`);
+    });
+  });
+
+  it('size render', async () => {
+    sizeList.forEach((s) => {
+      const wrapper = mount(() => <Avatar image={IMAGE} size={s}></Avatar>);
+      expect(wrapper.classes()).toContain(`t-size-${s?.slice(0, 1)}`);
+    });
+  });
+
+  it('badge render', async () => {
+    const wrapper = mount(() => <Avatar image={IMAGE} badgeProps={{ count: 10 }}></Avatar>);
+    expect(wrapper.findComponent(Badge).exists()).toBeTruthy();
+    const badge = wrapper.find('.t-badge > .t-badge__inner');
+    expect(badge.exists()).toBeTruthy();
+    expect(badge.text()).toBe('10');
+  });
+
+  it('error render', async () => {
+    const onError = vi.fn();
+    const wrapper = mount(() => <Avatar image={IMAGE_FAIL} alt="加载错误" onError={onError} />);
+    const img = wrapper.find('.t-avatar__inner > img');
+    expect(img.attributes('alt')).toBe('加载错误');
+    expect(img.exists()).toBeUndefined;
+    await nextTick();
+    await img.trigger('error');
+    expect(onError).toBeCalled();
+  });
+});
+
+describe('AvatarGroup.vue', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => (
+      <AvatarGroup>
+        <Avatar image={IMAGE}></Avatar>
+        <Avatar image={IMAGE}></Avatar>
+      </AvatarGroup>
+    ));
+    expect(wrapper.classes()).toContain('t-avatar-group');
+    expect(wrapper.classes()).toContain('t-avatar--offset-right');
+    const avatarList = wrapper.findAll('.t-avatar');
+    expect(avatarList.length).toBe(2);
+    expect(avatarList[0].classes()).toContain('t-avatar');
+    expect(avatarList[1].classes()).toContain('t-avatar');
+  });
+
+  it('cascading render', async () => {
+    const wrapper = mount(() => (
+      <AvatarGroup cascading="left-up">
+        <Avatar image={IMAGE}></Avatar>
+        <Avatar image={IMAGE}></Avatar>
+      </AvatarGroup>
+    ));
+    expect(wrapper.classes()).toContain('t-avatar--offset-left');
+  });
+
+  it('max render', async () => {
+    const wrapper = mount(() => (
+      <AvatarGroup max={1}>
+        <Avatar image={IMAGE}></Avatar>
+        <Avatar image={IMAGE}></Avatar>
+        <Avatar image={IMAGE}></Avatar>
+        <Avatar image={IMAGE}></Avatar>
+        <Avatar image={IMAGE}></Avatar>
+      </AvatarGroup>
+    ));
+    const avatarList = wrapper.findAllComponents(Avatar);
+    expect(avatarList.length).toBe(1);
+  });
+});
diff --git a/src/badge/__test__/index.test.jsx b/src/badge/__test__/index.test.jsx
new file mode 100644
index 00000000..b5f79167
--- /dev/null
+++ b/src/badge/__test__/index.test.jsx
@@ -0,0 +1,90 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Badge from '../badge.vue';
+import Button from '../../button/button.vue';
+
+const shapeList = ['circle', 'ribbon', 'round'];
+const sizeList = ['medium', 'small'];
+
+describe('Badge.vue', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => (
+      <Badge dot>
+        <Button>按钮</Button>
+      </Badge>
+    ));
+    expect(wrapper.classes()).toContain('t-badge');
+    const dot = wrapper.find('.t-badge--dot');
+    const button = wrapper.findComponent(Button);
+    expect(dot.exists()).toBeTruthy();
+    expect(button.exists()).toBeTruthy();
+  });
+
+  it('count render', async () => {
+    const wrapper = mount(() => (
+      <Badge count={5}>
+        <Button>按钮</Button>
+      </Badge>
+    ));
+    const inner = wrapper.find('.t-badge__inner');
+    expect(inner.exists()).toBeTruthy();
+    expect(inner.text()).toBe('5');
+  });
+
+  it('maxCount render', async () => {
+    const wrapper = mount(() => (
+      <Badge count={100} maxCount={50}>
+        <Button>按钮</Button>
+      </Badge>
+    ));
+    const inner = wrapper.find('.t-badge__inner');
+    expect(inner.exists()).toBeTruthy();
+    expect(inner.text()).toBe('50+');
+  });
+
+  it('shape render', async () => {
+    shapeList.forEach((s) => {
+      const wrapper = mount(() => (
+        <Badge shape={s} count={5}>
+          <Button>按钮</Button>
+        </Badge>
+      ));
+      const inner = wrapper.find('.t-badge__inner');
+      expect(inner.classes()).toContain(`t-badge--${s}`);
+    });
+  });
+
+  it('size render', async () => {
+    sizeList.forEach((s) => {
+      const wrapper = mount(() => (
+        <Badge size={s} count={5}>
+          <Button>按钮</Button>
+        </Badge>
+      ));
+      const inner = wrapper.find('.t-badge__inner');
+      expect(inner.classes()).toContain(`t-badge--${s}`);
+    });
+  });
+
+  it('showZero render', async () => {
+    const wrapper = mount(() => (
+      <Badge count={0} showZero>
+        <Button>按钮</Button>
+      </Badge>
+    ));
+    const inner = wrapper.find('.t-badge__inner');
+    expect(inner.exists()).toBeTruthy();
+    expect(inner.text()).toBe('0');
+  });
+
+  it('offset render', async () => {
+    const wrapper = mount(() => (
+      <Badge count={100} offset={[20, 30]}>
+        <Button>按钮</Button>
+      </Badge>
+    ));
+    const inner = wrapper.find('.t-badge__inner');
+    expect(getComputedStyle(inner.element, null).right).toBe('20px');
+    expect(getComputedStyle(inner.element, null).top).toBe('30px');
+  });
+});
diff --git a/src/button/__test__/__snapshots__/index.test.jsx.snap b/src/button/__test__/__snapshots__/index.test.jsx.snap
new file mode 100644
index 00000000..31a42d12
--- /dev/null
+++ b/src/button/__test__/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,34 @@
+// Vitest Snapshot v1
+
+exports[`button > button-group > button group render 1`] = `
+<div
+  class="t-button-group t-button-group--default"
+>
+  
+  <button
+    aria-disabled="false"
+    class="t-button t-size-m t-button--base t-button--default t-button--shape-rectangle"
+    role="button"
+  >
+    <!---->
+    <span
+      class="t-button__text"
+    >
+      tdesign-mobile-vue
+    </span>
+  </button>
+  <button
+    aria-disabled="false"
+    class="t-button t-size-m t-button--base t-button--default t-button--shape-rectangle"
+    role="button"
+  >
+    <!---->
+    <span
+      class="t-button__text"
+    >
+      tdesign-mobile-vue
+    </span>
+  </button>
+  
+</div>
+`;
diff --git a/src/button/__test__/index.test.jsx b/src/button/__test__/index.test.jsx
new file mode 100644
index 00000000..6c617fff
--- /dev/null
+++ b/src/button/__test__/index.test.jsx
@@ -0,0 +1,252 @@
+import { h, ref } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi } from 'vitest';
+import { AppIcon as TIconApp, LoadingIcon } from 'tdesign-icons-vue-next';
+import Button from '../button.vue';
+import ButtonGroup from '../../button-group/button-group.vue';
+
+const prefix = 't'
+const name = `${prefix}-button`;
+const SIZE_CLASSNAMES = {
+  small: `${prefix}-size-s`,
+  medium: `${prefix}-size-m`,
+  large: `${prefix}-size-l`,
+};
+const TEXT = 'tdesign-mobile-vue';
+const iconFunc = () => h(TIconApp);
+
+describe('button', () => {
+  describe('props', () => {
+    it(': theme', async () => {
+      const wrapper = mount(Button, {
+        props: {
+          theme: '',
+          content: TEXT
+        }
+      })
+      // theme = ''
+      const $button = wrapper.findComponent(Button);
+      ['default', 'primary', 'danger'].map(item => {
+        expect($button.classes().includes(`${name}--${item}`)).toBeFalsy();
+      })
+      expect(wrapper.text()).toBe(TEXT);
+      const theme = 'danger';
+      await wrapper.setProps({
+        theme,
+      });
+      // theme = 'danger'
+      expect($button.classes()).toContain(`${name}--${theme}`);
+    });
+
+    it(': content', async () => {
+      const wrapper = mount(Button, {
+        props: {
+          content: TEXT
+        }
+      })
+      const $button = wrapper.findComponent(Button);
+      expect($button.text()).toBe(TEXT);
+
+      const newContent = 'new content';
+      await wrapper.setProps({
+        content: newContent
+      });
+      expect($button.text()).toBe(newContent);
+    });
+
+    it(': shape', async () => {
+      const wrapper = mount(Button, {
+        props: {
+          shape: '',
+          content: TEXT
+        }
+      })
+      // shape = ''
+      const $button = wrapper.findComponent(Button);
+      ['rectangle', 'square', 'round', 'circle'].map(item => {
+        expect($button.classes().includes(`${name}--shape-${item}`)).toBeFalsy();
+      })
+      expect(wrapper.text()).toBe(TEXT);
+      const shape = 'square';
+      await wrapper.setProps({
+        shape,
+      });
+      // shape = 'square'
+      expect($button.classes()).toContain(`${name}--shape-${shape}`);
+    });
+
+    it(': size', async () => {
+      const wrapper = mount(Button, {
+        props: {
+          size: '',
+          content: TEXT
+        }
+      })
+      // size = ''
+      const $button = wrapper.findComponent(Button);
+      ['small', 'medium', 'large'].map(item => {
+        expect($button.classes().includes(`${SIZE_CLASSNAMES[item]}`)).toBeFalsy();
+      });
+      expect(wrapper.text()).toBe(TEXT);
+      const size = 'small';
+      await wrapper.setProps({
+        size,
+      });
+      // size = 'small'
+      expect($button.classes()).toContain(`${SIZE_CLASSNAMES[size]}`);
+    });
+
+    it(': variant', async () => {
+      const wrapper = mount(Button, {
+        props: {
+          variant: '',
+          content: TEXT
+        }
+      })
+      // variant = ''
+      const $button = wrapper.findComponent(Button);
+      ['base', 'outline', 'text'].map(item => {
+        expect($button.classes().includes(`${name}--${item}`)).toBeFalsy();
+      })
+      expect(wrapper.text()).toBe(TEXT);
+      const variant = 'outline';
+      await wrapper.setProps({
+        variant,
+      });
+      // variant = 'outline'
+      expect($button.classes()).toContain(`${name}--${variant}`);
+    });
+
+    it(': disabled', async () => {
+      const onClick = vi.fn();
+      const disabled = true;
+      const wrapper = mount(Button, {
+        props: {
+          disabled,
+          content: TEXT,
+          onClick,
+        },
+      })
+      const $button = wrapper.findComponent(Button);
+      // disabled = true, 不会触发 click
+      expect($button.classes()).toContain('t-is-disabled');
+      await $button.trigger('click');
+      expect(onClick).not.toBeCalled();
+
+      // disabled = false, 可以触发 click
+      await wrapper.setProps({
+        disabled: false,
+      });
+      await $button.trigger('click');
+      expect(onClick).toBeCalled();
+    });
+
+    it(': loading', async () => {
+      const loading = true;
+      const onClick = vi.fn();
+      const wrapper = mount(Button, {
+        props: {
+          loading,
+          content: TEXT,
+          onClick,
+        },
+      })
+      const $button = wrapper.findComponent(Button);
+
+      // loading = true，不触发 click
+      expect($button.classes()).toContain('t-is-loading');
+      expect($button.findComponent(LoadingIcon).exists()).toBeTruthy();
+      await $button.trigger('click');
+      expect(onClick).not.toBeCalled();
+      // loading = false
+      await wrapper.setProps({
+        loading: false,
+      });
+      expect($button.findComponent(LoadingIcon).exists()).toBeFalsy();
+      await $button.trigger('click');
+      expect(onClick).toBeCalled();
+    });
+
+    it(': icon', async () => {
+      const wrapper = mount(Button, {
+        props: {
+          loading: true,
+          icon: iconFunc,
+          content: TEXT,
+        },
+      })
+      const $button = wrapper.findComponent(Button);
+      expect($button.classes()).toContain('t-is-loading');
+      expect($button.findComponent(LoadingIcon).exists()).toBeTruthy();
+      expect($button.findComponent(TIconApp).exists()).toBeFalsy();
+    });
+
+    it(': ghost', async () => {
+      const ghost = true;
+      const wrapper = mount(Button, {
+        props: {
+          ghost,
+          content: TEXT,
+        },
+      })
+      const $button = wrapper.findComponent(Button);
+
+      // ghost = true
+      expect($button.classes().includes(`${name}--ghost`)).toBeTruthy();
+      // ghost = false
+      await wrapper.setProps({
+        ghost: false,
+      });
+      expect($button.classes().includes(`${name}--ghost`)).toBeFalsy();
+    });
+
+    it(': block', async () => {
+      const block = true;
+      const wrapper = mount(Button, {
+        props: {
+          block,
+          content: TEXT,
+        },
+      })
+      const $button = wrapper.findComponent(Button);
+
+      // block = true
+      expect($button.classes().includes(`${prefix}-is-block`)).toBeTruthy();
+      // block = false
+      await wrapper.setProps({
+        block: false,
+      });
+      expect($button.classes().includes(`${prefix}-is-block`)).toBeFalsy();
+    });
+  });
+
+  describe('button-group', () => {
+    it('button group render', async () => {
+      const items = [
+        {
+          content: TEXT,
+        },
+        {
+          content: TEXT,
+        },
+      ]
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <ButtonGroup>
+              {{
+                default: items.map((item, index) => {
+                  return <Button content={item.content} />
+                })
+              }}
+            </ButtonGroup>
+          )
+        }
+      });
+      expect(wrapper.element).toMatchSnapshot();
+      const $buttonGroup = wrapper.findComponent(ButtonGroup)
+      expect($buttonGroup.exists()).toBeTruthy();
+      expect(wrapper.findAllComponents(Button).length).toEqual(items.length);
+    });
+  });
+});
diff --git a/src/calendar/__test__/__snapshots__/demo.test.jsx.snap b/src/calendar/__test__/__snapshots__/demo.test.jsx.snap
index 7e5161fd..1f9379ef 100644
--- a/src/calendar/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/calendar/__test__/__snapshots__/demo.test.jsx.snap
@@ -7,8 +7,18 @@ exports[`Calendar > Calendar baseVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -18,7 +28,12 @@ exports[`Calendar > Calendar baseVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -2378,8 +2393,18 @@ exports[`Calendar > Calendar customButtonVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -2389,7 +2414,12 @@ exports[`Calendar > Calendar customButtonVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -4750,8 +4780,18 @@ exports[`Calendar > Calendar customRangeVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -4761,7 +4801,12 @@ exports[`Calendar > Calendar customRangeVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -5234,8 +5279,18 @@ exports[`Calendar > Calendar customTextVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -5245,7 +5300,12 @@ exports[`Calendar > Calendar customTextVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -6294,8 +6354,18 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
           <div
             class="t-popup"
           >
-            <transition-stub>
-              <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade"
+              persisted="false"
+            >
+              <transition-stub
+                appear="false"
+                css="true"
+                name="t-overlay"
+                persisted="true"
+              >
                 <div
                   class="t-overlay"
                   style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -6305,7 +6375,12 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
                 </div>
               </transition-stub>
             </transition-stub>
-            <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="slide-bottom"
+              persisted="true"
+            >
               <div
                 class="t-popup--content t-popup--content-bottom"
                 style="display: none;"
@@ -8659,8 +8734,18 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
           <div
             class="t-popup"
           >
-            <transition-stub>
-              <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade"
+              persisted="false"
+            >
+              <transition-stub
+                appear="false"
+                css="true"
+                name="t-overlay"
+                persisted="true"
+              >
                 <div
                   class="t-overlay"
                   style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -8670,7 +8755,12 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
                 </div>
               </transition-stub>
             </transition-stub>
-            <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="slide-bottom"
+              persisted="true"
+            >
               <div
                 class="t-popup--content t-popup--content-bottom"
                 style="display: none;"
@@ -11024,8 +11114,18 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
           <div
             class="t-popup"
           >
-            <transition-stub>
-              <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade"
+              persisted="false"
+            >
+              <transition-stub
+                appear="false"
+                css="true"
+                name="t-overlay"
+                persisted="true"
+              >
                 <div
                   class="t-overlay"
                   style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -11035,7 +11135,12 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
                 </div>
               </transition-stub>
             </transition-stub>
-            <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="slide-bottom"
+              persisted="true"
+            >
               <div
                 class="t-popup--content t-popup--content-bottom"
                 style="display: none;"
@@ -13406,8 +13511,18 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -13417,7 +13532,12 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-bottom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-bottom"
             style="display: none;"
@@ -14432,8 +14552,18 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -14443,7 +14573,12 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-bottom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-bottom"
             style="display: none;"
@@ -16797,8 +16932,18 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -16808,7 +16953,12 @@ exports[`Calendar > Calendar mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-bottom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-bottom"
             style="display: none;"
@@ -17297,8 +17447,18 @@ exports[`Calendar > Calendar multipleVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -17308,7 +17468,12 @@ exports[`Calendar > Calendar multipleVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -19665,8 +19830,18 @@ exports[`Calendar > Calendar rangeVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -19676,7 +19851,12 @@ exports[`Calendar > Calendar rangeVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
diff --git a/src/cell/__test__/index.test.jsx b/src/cell/__test__/index.test.jsx
new file mode 100644
index 00000000..0d8b9bb0
--- /dev/null
+++ b/src/cell/__test__/index.test.jsx
@@ -0,0 +1,98 @@
+import { h } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import { ChevronRightIcon, AppIcon } from 'tdesign-icons-vue-next';
+import Cell from '../cell.vue';
+import Avatar from '../../avatar/avatar.vue';
+import { TdCellProps } from '../type';
+
+const appIcon = () => h(AppIcon);
+const chevronRightIcon = () => h(ChevronRightIcon);
+const alignList = ['bottom', 'middle', 'top'];
+const avatarUrl = 'https://tdesign.gtimg.com/mobile/demos/avatar_1.png';
+
+describe('Cell.vue', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Cell title="单行标题"></Cell>);
+    expect(wrapper.classes()).toContain('t-cell');
+    const title = wrapper.find('.t-cell__title');
+    expect(title.exists()).toBeTruthy();
+    expect(title.text()).toBe('单行标题');
+  });
+
+  it('required render', async () => {
+    const wrapper = mount(() => <Cell title="单行标题" required></Cell>);
+    const required = wrapper.find('.t-cell__title > .t-cell--required');
+    expect(required.exists()).toBeTruthy();
+    expect(required.text()).toContain('*');
+  });
+
+  it('arrow render', async () => {
+    const wrapper = mount(() => <Cell title="单行标题" arrow></Cell>);
+    const icon = wrapper.find('.t-cell__right-icon');
+    expect(icon.exists()).toBeTruthy();
+    expect(wrapper.findComponent(ChevronRightIcon).exists()).toBeTruthy();
+  });
+
+  it('note render', async () => {
+    const wrapper = mount(() => <Cell title="单行标题" note="辅助信息"></Cell>);
+    const note = wrapper.find('.t-cell__note');
+    expect(note.exists()).toBeTruthy();
+    expect(note.text()).toBe('辅助信息');
+  });
+
+  it('left icon render', async () => {
+    const wrapper = mount(() => <Cell title="单行标题" leftIcon={appIcon}></Cell>);
+    const icon = wrapper.find('.t-cell__left-icon');
+    expect(icon.exists()).toBeTruthy();
+    expect(wrapper.findComponent(AppIcon).exists()).toBeTruthy();
+  });
+
+  it('border render', async () => {
+    const wrapper1 = mount(() => <Cell title="单行标题"></Cell>);
+    expect(wrapper1.classes()).toContain('t-cell--bordered');
+    const wrapper2 = mount(() => <Cell title="单行标题" bordered={false}></Cell>);
+    expect(wrapper2.classes('t-cell--bordered')).toBeFalsy();
+  });
+
+  it('align render', async () => {
+    alignList.forEach((a) => {
+      const wrapper = mount(() => <Cell title="单行标题" align={a}></Cell>);
+      expect(wrapper.classes()).toContain(`t-cell--${a}`);
+    });
+  });
+
+  it('description render', async () => {
+    const wrapper = mount(() => <Cell title="单行标题" description="描述信息"></Cell>);
+    const desc = wrapper.find('.t-cell__description');
+    expect(desc.exists()).toBeTruthy();
+    expect(desc.text()).toBe('描述信息');
+  });
+
+  it('image render', async () => {
+    const wrapper = mount(() => <Cell title="单行标题" image={avatarUrl}></Cell>);
+    const img = wrapper.find('.t-cell__left-icon > img');
+    expect(img.exists()).toBeTruthy();
+    expect(img.attributes('src')).toBe(avatarUrl);
+  });
+
+  it('slot render', async () => {
+    const slots = {
+      leftIcon: () => <Avatar shape="circle" image={avatarUrl}></Avatar>,
+      rightIcon: () => <chevronRightIcon />,
+    };
+    const wrapper = mount(() => <Cell title="单行标题" v-slots={slots}></Cell>);
+    const leftIcon = wrapper.find('.t-cell__left-icon');
+    expect(leftIcon.exists()).toBeTruthy();
+    expect(leftIcon.findComponent(Avatar).exists()).toBeTruthy();
+    const rightIcon = wrapper.find('.t-cell__right-icon');
+    expect(rightIcon.exists()).toBeTruthy();
+    expect(rightIcon.findComponent(chevronRightIcon).exists()).toBeTruthy();
+  });
+
+  it('click trigger', async () => {
+    const wrapper = mount(() => <Cell title="单行标题"></Cell>);
+    await wrapper.trigger('click');
+    expect(wrapper.emitted('click')).toBeDefined();
+  });
+});
diff --git a/src/date-time-picker/__test__/__snapshots__/demo.test.jsx.snap b/src/date-time-picker/__test__/__snapshots__/demo.test.jsx.snap
index 62a66905..5776e719 100644
--- a/src/date-time-picker/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/date-time-picker/__test__/__snapshots__/demo.test.jsx.snap
@@ -56,8 +56,18 @@ exports[`DateTimePicker > DateTimePicker baseVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -67,7 +77,12 @@ exports[`DateTimePicker > DateTimePicker baseVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -1305,8 +1320,18 @@ exports[`DateTimePicker > DateTimePicker hourMinuteVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1316,7 +1341,12 @@ exports[`DateTimePicker > DateTimePicker hourMinuteVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -1911,8 +1941,18 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1922,7 +1962,12 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -3149,8 +3194,18 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -3160,7 +3215,12 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -3634,8 +3694,18 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -3645,7 +3715,12 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -3953,8 +4028,18 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -3964,7 +4049,12 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -4543,8 +4633,18 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -4554,7 +4654,12 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -5697,8 +5802,18 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -5708,7 +5823,12 @@ exports[`DateTimePicker > DateTimePicker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -6947,8 +7067,18 @@ exports[`DateTimePicker > DateTimePicker renderLabelDateVue demo works fine 1`]
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -6958,7 +7088,12 @@ exports[`DateTimePicker > DateTimePicker renderLabelDateVue demo works fine 1`]
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -7443,8 +7578,18 @@ exports[`DateTimePicker > DateTimePicker startEndDateVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -7454,7 +7599,12 @@ exports[`DateTimePicker > DateTimePicker startEndDateVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
diff --git a/src/dialog/__test__/__snapshots__/demo.test.jsx.snap b/src/dialog/__test__/__snapshots__/demo.test.jsx.snap
index 9a17085c..7c558adc 100644
--- a/src/dialog/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/dialog/__test__/__snapshots__/demo.test.jsx.snap
@@ -64,8 +64,18 @@ exports[`Dialog > Dialog confirmVue demo works fine 1`] = `
           <div
             class="t-popup"
           >
-            <transition-stub>
-              <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade"
+              persisted="false"
+            >
+              <transition-stub
+                appear="false"
+                css="true"
+                name="t-overlay"
+                persisted="true"
+              >
                 <div
                   class="t-overlay"
                   style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -75,7 +85,12 @@ exports[`Dialog > Dialog confirmVue demo works fine 1`] = `
                 </div>
               </transition-stub>
             </transition-stub>
-            <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade-zoom"
+              persisted="true"
+            >
               <div
                 class="t-popup--content t-popup--content-center"
                 style="display: none;"
@@ -128,8 +143,18 @@ exports[`Dialog > Dialog confirmVue demo works fine 1`] = `
           <div
             class="t-popup"
           >
-            <transition-stub>
-              <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade"
+              persisted="false"
+            >
+              <transition-stub
+                appear="false"
+                css="true"
+                name="t-overlay"
+                persisted="true"
+              >
                 <div
                   class="t-overlay"
                   style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -139,7 +164,12 @@ exports[`Dialog > Dialog confirmVue demo works fine 1`] = `
                 </div>
               </transition-stub>
             </transition-stub>
-            <transition-stub>
+            <transition-stub
+              appear="false"
+              css="true"
+              name="fade-zoom"
+              persisted="true"
+            >
               <div
                 class="t-popup--content t-popup--content-center"
                 style="display: none;"
@@ -252,8 +282,18 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
             <div
               class="t-popup"
             >
-              <transition-stub>
-                <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade"
+                persisted="false"
+              >
+                <transition-stub
+                  appear="false"
+                  css="true"
+                  name="t-overlay"
+                  persisted="true"
+                >
                   <div
                     class="t-overlay"
                     style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -263,7 +303,12 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
                   </div>
                 </transition-stub>
               </transition-stub>
-              <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade-zoom"
+                persisted="true"
+              >
                 <div
                   class="t-popup--content t-popup--content-center"
                   style="display: none;"
@@ -341,8 +386,18 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
             <div
               class="t-popup"
             >
-              <transition-stub>
-                <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade"
+                persisted="false"
+              >
+                <transition-stub
+                  appear="false"
+                  css="true"
+                  name="t-overlay"
+                  persisted="true"
+                >
                   <div
                     class="t-overlay"
                     style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -352,7 +407,12 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
                   </div>
                 </transition-stub>
               </transition-stub>
-              <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade-zoom"
+                persisted="true"
+              >
                 <div
                   class="t-popup--content t-popup--content-center"
                   style="display: none;"
@@ -430,8 +490,18 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
             <div
               class="t-popup"
             >
-              <transition-stub>
-                <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade"
+                persisted="false"
+              >
+                <transition-stub
+                  appear="false"
+                  css="true"
+                  name="t-overlay"
+                  persisted="true"
+                >
                   <div
                     class="t-overlay"
                     style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -441,7 +511,12 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
                   </div>
                 </transition-stub>
               </transition-stub>
-              <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade-zoom"
+                persisted="true"
+              >
                 <div
                   class="t-popup--content t-popup--content-center"
                   style="display: none;"
@@ -523,8 +598,18 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
             <div
               class="t-popup"
             >
-              <transition-stub>
-                <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade"
+                persisted="false"
+              >
+                <transition-stub
+                  appear="false"
+                  css="true"
+                  name="t-overlay"
+                  persisted="true"
+                >
                   <div
                     class="t-overlay"
                     style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -534,7 +619,12 @@ exports[`Dialog > Dialog feedbackVue demo works fine 1`] = `
                   </div>
                 </transition-stub>
               </transition-stub>
-              <transition-stub>
+              <transition-stub
+                appear="false"
+                css="true"
+                name="fade-zoom"
+                persisted="true"
+              >
                 <div
                   class="t-popup--content t-popup--content-center"
                   style="display: none;"
@@ -645,8 +735,18 @@ exports[`Dialog > Dialog inputVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -656,7 +756,12 @@ exports[`Dialog > Dialog inputVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade-zoom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-center"
         style="display: none;"
@@ -701,8 +806,18 @@ exports[`Dialog > Dialog inputVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -712,7 +827,12 @@ exports[`Dialog > Dialog inputVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade-zoom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-center"
         style="display: none;"
@@ -865,8 +985,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -876,7 +1006,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -932,8 +1067,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -943,7 +1088,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -999,8 +1149,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1010,7 +1170,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -1074,8 +1239,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1085,7 +1260,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -1197,8 +1377,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1208,7 +1398,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -1283,8 +1478,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1294,7 +1499,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -1418,8 +1628,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1429,7 +1649,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -1496,8 +1721,18 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1507,7 +1742,12 @@ exports[`Dialog > Dialog mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
diff --git a/src/divider/__test__/__snapshots__/demo.test.jsx.snap b/src/divider/__test__/__snapshots__/demo.test.jsx.snap
index ed4a2dbe..a12cedba 100644
--- a/src/divider/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/divider/__test__/__snapshots__/demo.test.jsx.snap
@@ -2,123 +2,130 @@
 
 exports[`Divider > Divider alignVue demo works fine 1`] = `
 <div
-  class="demo-6"
+  data-v-7ac68ae7=""
 >
-  <span>
-    正直
-  </span>
-  <div
-    class="t-divider t-divider--hairline t-divider--content-left"
-    role="separator"
+  <h3
+    data-v-7ac68ae7=""
   >
-    
-    TDesign
-    
-  </div>
-  <span>
-    进取
-  </span>
+    文字+直线
+  </h3>
   <div
-    class="t-divider t-divider--hairline t-divider--content-center"
+    class="t-divider t-divider--hairline t-divider--content-center demo-5"
+    data-v-7ac68ae7=""
     role="separator"
   >
     
-    TDesign
+     文字信息 
     
   </div>
-  <span>
-    合作
-  </span>
+  <h3
+    data-v-7ac68ae7=""
+  >
+    纯文字
+  </h3>
   <div
-    class="t-divider t-divider--hairline t-divider--content-right"
+    class="t-divider t-divider--hairline t-divider--content-center"
+    data-v-7ac68ae7=""
     role="separator"
+    style="border-color: transparent;"
   >
     
-    TDesign
+     没有更多了~ 
     
   </div>
-  <span>
-    创新
-  </span>
 </div>
 `;
 
 exports[`Divider > Divider baseVue demo works fine 1`] = `
 <div
-  class="demo-6"
+  data-v-589c0842=""
 >
-  <h3>
+  <h3
+    data-v-589c0842=""
+  >
     直线拉通
   </h3>
   <div
     class="t-divider t-divider--hairline"
+    data-v-589c0842=""
     role="separator"
   >
     <!---->
   </div>
-  <h3>
-    直线拉通
+  <h3
+    data-v-589c0842=""
+  >
+    虚线拉通
   </h3>
+  <div
+    class="t-divider t-divider--hairline t-divider--dashed"
+    data-v-589c0842=""
+    role="separator"
+  >
+    <!---->
+  </div>
 </div>
 `;
 
 exports[`Divider > Divider horizontalVue demo works fine 1`] = `
 <div
-  class="demo-6"
+  data-v-63a80f32=""
 >
-  <span>
-    正直
-  </span>
+  <h3
+    data-v-63a80f32=""
+  >
+    左右间距
+  </h3>
   <div
-    class="t-divider t-divider--hairline"
+    class="t-divider t-divider--hairline demo-2"
+    data-v-63a80f32=""
     role="separator"
   >
     <!---->
   </div>
-  <span>
-    进取
-  </span>
+  <h3
+    data-v-63a80f32=""
+  >
+    右侧拉通
+  </h3>
   <div
-    class="t-divider t-divider--hairline"
+    class="t-divider t-divider--hairline demo-3"
+    data-v-63a80f32=""
     role="separator"
   >
     <!---->
   </div>
-  <span>
-    合作
-  </span>
+  <h3
+    data-v-63a80f32=""
+  >
+    自定义左侧间距
+  </h3>
   <div
-    class="t-divider t-divider--hairline"
+    class="t-divider t-divider--hairline demo-4"
+    data-v-63a80f32=""
     role="separator"
   >
     <!---->
   </div>
-  <span>
-    创新
-  </span>
 </div>
 `;
 
 exports[`Divider > Divider mobileVue demo works fine 1`] = `
 <div
   class="tdesign-mobile-demo"
-  data-v-bf2b60a0=""
 >
   <h1
     class="title"
-    data-v-bf2b60a0=""
   >
     Divider 分割符
   </h1>
   <p
     class="summary"
-    data-v-bf2b60a0=""
   >
      分割线是一个呈线状的轻量化组件，起到分割、组织、细化的作用，用于有逻辑的组织元素内容和页面结构。 
   </p>
   <div
     class="tdesign-mobile-demo-block"
-    data-v-bf2b60a0=""
   >
     <h2
       class="tdesign-mobile-demo-block__title"
@@ -132,136 +139,151 @@ exports[`Divider > Divider mobileVue demo works fine 1`] = `
     </p>
     
     <div
-      data-v-bf2b60a0=""
       style="background: rgb(255, 255, 255);"
     >
-      <h3
-        data-v-bf2b60a0=""
-      >
-        直线拉通
-      </h3>
       <div
-        class="t-divider t-divider--hairline"
-        data-v-bf2b60a0=""
-        role="separator"
-      >
-        <!---->
-      </div>
-      <h3
-        data-v-bf2b60a0=""
+        data-v-589c0842=""
       >
-        虚线拉通
-      </h3>
-      <div
-        class="t-divider t-divider--hairline t-divider--dashed"
-        data-v-bf2b60a0=""
-        role="separator"
-      >
-        <!---->
-      </div>
-      <h3
-        data-v-bf2b60a0=""
-      >
-        左右间距
-      </h3>
-      <div
-        class="t-divider t-divider--hairline demo-2"
-        data-v-bf2b60a0=""
-        role="separator"
-      >
-        <!---->
-      </div>
-      <h3
-        data-v-bf2b60a0=""
-      >
-        右侧拉通
-      </h3>
-      <div
-        class="t-divider t-divider--hairline demo-3"
-        data-v-bf2b60a0=""
-        role="separator"
-      >
-        <!---->
-      </div>
-      <h3
-        data-v-bf2b60a0=""
-      >
-        自定义左侧间距
-      </h3>
-      <div
-        class="t-divider t-divider--hairline demo-4"
-        data-v-bf2b60a0=""
-        role="separator"
-      >
-        <!---->
-      </div>
-      <h3
-        data-v-bf2b60a0=""
-      >
-        文字+直线
-      </h3>
-      <div
-        class="t-divider t-divider--hairline t-divider--content-center demo-5"
-        data-v-bf2b60a0=""
-        role="separator"
-      >
-        
-         文字信息 
-        
+        <h3
+          data-v-589c0842=""
+        >
+          直线拉通
+        </h3>
+        <div
+          class="t-divider t-divider--hairline"
+          data-v-589c0842=""
+          role="separator"
+        >
+          <!---->
+        </div>
+        <h3
+          data-v-589c0842=""
+        >
+          虚线拉通
+        </h3>
+        <div
+          class="t-divider t-divider--hairline t-divider--dashed"
+          data-v-589c0842=""
+          role="separator"
+        >
+          <!---->
+        </div>
       </div>
-      <h3
-        data-v-bf2b60a0=""
-      >
-        纯文字
-      </h3>
       <div
-        class="t-divider t-divider--hairline t-divider--content-center"
-        data-v-bf2b60a0=""
-        role="separator"
-        style="border-color: transparent;"
+        data-v-63a80f32=""
       >
-        
-         没有更多了~ 
-        
+        <h3
+          data-v-63a80f32=""
+        >
+          左右间距
+        </h3>
+        <div
+          class="t-divider t-divider--hairline demo-2"
+          data-v-63a80f32=""
+          role="separator"
+        >
+          <!---->
+        </div>
+        <h3
+          data-v-63a80f32=""
+        >
+          右侧拉通
+        </h3>
+        <div
+          class="t-divider t-divider--hairline demo-3"
+          data-v-63a80f32=""
+          role="separator"
+        >
+          <!---->
+        </div>
+        <h3
+          data-v-63a80f32=""
+        >
+          自定义左侧间距
+        </h3>
+        <div
+          class="t-divider t-divider--hairline demo-4"
+          data-v-63a80f32=""
+          role="separator"
+        >
+          <!---->
+        </div>
       </div>
-      <h3
-        data-v-bf2b60a0=""
-      >
-        垂直分割
-      </h3>
       <div
-        class="demo-6"
-        data-v-bf2b60a0=""
+        data-v-7ac68ae7=""
       >
-        <span
-          data-v-bf2b60a0=""
+        <h3
+          data-v-7ac68ae7=""
         >
-          文字信息
-        </span>
+          文字+直线
+        </h3>
         <div
-          class="t-divider t-divider--hairline t-divider-vertical"
-          data-v-bf2b60a0=""
+          class="t-divider t-divider--hairline t-divider--content-center demo-5"
+          data-v-7ac68ae7=""
           role="separator"
         >
-          <!---->
+          
+           文字信息 
+          
         </div>
-        <span
-          data-v-bf2b60a0=""
+        <h3
+          data-v-7ac68ae7=""
         >
-          文字信息
-        </span>
+          纯文字
+        </h3>
         <div
-          class="t-divider t-divider--hairline t-divider-vertical"
-          data-v-bf2b60a0=""
+          class="t-divider t-divider--hairline t-divider--content-center"
+          data-v-7ac68ae7=""
           role="separator"
+          style="border-color: transparent;"
         >
-          <!---->
+          
+           没有更多了~ 
+          
         </div>
-        <span
-          data-v-bf2b60a0=""
+      </div>
+      <div
+        data-v-0851d1f8=""
+      >
+        <h3
+          data-v-0851d1f8=""
         >
-          文字信息
-        </span>
+          垂直分割
+        </h3>
+        <div
+          class="demo-6"
+          data-v-0851d1f8=""
+        >
+          <span
+            data-v-0851d1f8=""
+          >
+            文字信息
+          </span>
+          <div
+            class="t-divider t-divider--hairline t-divider-vertical"
+            data-v-0851d1f8=""
+            role="separator"
+          >
+            <!---->
+          </div>
+          <span
+            data-v-0851d1f8=""
+          >
+            文字信息
+          </span>
+          <div
+            class="t-divider t-divider--hairline t-divider-vertical"
+            data-v-0851d1f8=""
+            role="separator"
+          >
+            <!---->
+          </div>
+          <span
+            data-v-0851d1f8=""
+          >
+            文字信息
+          </span>
+        </div>
       </div>
     </div>
     
@@ -271,49 +293,46 @@ exports[`Divider > Divider mobileVue demo works fine 1`] = `
 
 exports[`Divider > Divider verticalVue demo works fine 1`] = `
 <div
-  class="demo-6"
   data-v-0851d1f8=""
 >
-  <span
+  <h3
     data-v-0851d1f8=""
   >
-    正直信息
-  </span>
-  <div
-    class="t-divider t-divider--hairline t-divider-vertical"
-    data-v-0851d1f8=""
-    role="separator"
-  >
-    <!---->
-  </div>
-  <span
-    data-v-0851d1f8=""
-  >
-    进取
-  </span>
-  <div
-    class="t-divider t-divider--hairline t-divider-vertical"
-    data-v-0851d1f8=""
-    role="separator"
-  >
-    <!---->
-  </div>
-  <span
-    data-v-0851d1f8=""
-  >
-    合作
-  </span>
+    垂直分割
+  </h3>
   <div
-    class="t-divider t-divider--hairline t-divider-vertical"
+    class="demo-6"
     data-v-0851d1f8=""
-    role="separator"
   >
-    <!---->
+    <span
+      data-v-0851d1f8=""
+    >
+      文字信息
+    </span>
+    <div
+      class="t-divider t-divider--hairline t-divider-vertical"
+      data-v-0851d1f8=""
+      role="separator"
+    >
+      <!---->
+    </div>
+    <span
+      data-v-0851d1f8=""
+    >
+      文字信息
+    </span>
+    <div
+      class="t-divider t-divider--hairline t-divider-vertical"
+      data-v-0851d1f8=""
+      role="separator"
+    >
+      <!---->
+    </div>
+    <span
+      data-v-0851d1f8=""
+    >
+      文字信息
+    </span>
   </div>
-  <span
-    data-v-0851d1f8=""
-  >
-    创新
-  </span>
 </div>
 `;
diff --git a/src/divider/__test__/index.test.jsx b/src/divider/__test__/index.test.jsx
new file mode 100644
index 00000000..004270b5
--- /dev/null
+++ b/src/divider/__test__/index.test.jsx
@@ -0,0 +1,105 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Divider from '../divider.vue';
+
+const prefix = 't'
+const name = `${prefix}-divider`;
+const TEXT = 'tdesign-mobile-vue';
+
+describe('divider.vue', async () => {
+  describe('props', async () => {
+    it(': default', async () => {
+      const wrapper = mount(() => <Divider default={TEXT}></Divider>);
+      expect(wrapper.text()).toBe(TEXT);
+    });
+
+    it(': dashed', async () => {
+      const wrapper = mount(() => <Divider dashed />);
+      expect(wrapper.classes()).toContain(`${name}--dashed`);
+    });
+
+    it(': align', async () => {
+      const wrapper = mount(Divider, {
+        props: {
+          align: '',
+          content: TEXT
+        }
+      })
+      const $divider = wrapper.findComponent(Divider);
+      expect($divider.text()).toEqual(TEXT);
+
+      // align = ''
+      ['left', 'right', 'center'].map(item => {
+        expect($divider.classes().includes(`${name}--content-${item}`)).toBeFalsy();
+      })
+
+      const align = 'left';
+      await wrapper.setProps({
+        align,
+      });
+      // align = 'left'
+      expect($divider.classes()).toContain(`${name}--content-${align}`);
+    });
+
+    it(': layout', async () => {
+      const wrapper = mount(Divider, {
+        props: {
+          layout: '',
+          content: TEXT
+        }
+      })
+      const $divider = wrapper.findComponent(Divider);
+      expect($divider.text()).toEqual(TEXT);
+
+      // layout = ''
+      ['horizontal', 'vertical'].map(item => {
+        expect($divider.classes().includes(`${name}-${item}`)).toBeFalsy();
+      })
+
+      const layout = 'vertical';
+      await wrapper.setProps({
+        layout,
+      });
+      // layout = 'vertical'
+      expect($divider.classes()).toContain(`${name}-${layout}`);
+    });
+
+    it(': lineColor', async () => {
+      const wrapper = mount(() => <Divider lineColor="red" />);
+      expect(getComputedStyle(wrapper.element, null).borderColor).toBe('red');
+    });
+  });
+  describe('slot', async () => {
+    it(': content', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Divider>
+              {{
+                content: TEXT
+              }}
+            </Divider>
+          )
+        }
+      });
+      const $divider = wrapper.findComponent(Divider);
+      expect($divider.text()).toEqual(TEXT);
+    })
+
+    it(': default', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Divider>
+              {{
+                default: TEXT
+              }}
+            </Divider>
+          )
+        }
+      });
+      const $divider = wrapper.findComponent(Divider);
+      expect($divider.text()).toEqual(TEXT);
+    })
+  });
+});
diff --git a/src/drawer/__test__/__snapshots__/demo.test.jsx.snap b/src/drawer/__test__/__snapshots__/demo.test.jsx.snap
index 045fe2c4..04807c39 100644
--- a/src/drawer/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/drawer/__test__/__snapshots__/demo.test.jsx.snap
@@ -9,8 +9,18 @@ exports[`Drawer > Drawer baseVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -20,7 +30,12 @@ exports[`Drawer > Drawer baseVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-left"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-left"
         style="display: none;"
@@ -129,8 +144,18 @@ exports[`Drawer > Drawer iconVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -140,7 +165,12 @@ exports[`Drawer > Drawer iconVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-left"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-left"
         style="display: none;"
@@ -372,8 +402,18 @@ exports[`Drawer > Drawer mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -383,7 +423,12 @@ exports[`Drawer > Drawer mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-left"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-left"
             style="display: none;"
@@ -501,8 +546,18 @@ exports[`Drawer > Drawer mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -512,7 +567,12 @@ exports[`Drawer > Drawer mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-left"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-left"
             style="display: none;"
diff --git a/src/dropdown-menu/__test__/__snapshots__/index.test.jsx.snap b/src/dropdown-menu/__test__/__snapshots__/index.test.jsx.snap
new file mode 100644
index 00000000..390d5d88
--- /dev/null
+++ b/src/dropdown-menu/__test__/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,617 @@
+// Vitest Snapshot v1
+
+exports[`dropdown-menu > DropdownItem Props > : optionsLayout 1`] = `
+<div
+  class="t-dropdown-menu"
+>
+  <div
+    class="t-dropdown-menu__bar"
+  >
+    
+    <div
+      class="t-dropdown-menu__item"
+    >
+      <div
+        class="t-dropdown-menu__title"
+      >
+        树形双列
+      </div>
+      <svg
+        class="t-icon t-icon-caret-down-small t-dropdown-menu__arrow"
+        fill="none"
+        height="1em"
+        viewBox="0 0 16 16"
+        width="1em"
+      >
+        <path
+          d="M11 6H5l3 4.5L11 6z"
+          fill="currentColor"
+          fill-opacity="0.9"
+        />
+      </svg>
+    </div>
+    <div
+      class="t-dropdown-menu__item"
+    >
+      <div
+        class="t-dropdown-menu__title"
+      >
+        树形三列
+      </div>
+      <svg
+        class="t-icon t-icon-caret-down-small t-dropdown-menu__arrow"
+        fill="none"
+        height="1em"
+        viewBox="0 0 16 16"
+        width="1em"
+      >
+        <path
+          d="M11 6H5l3 4.5L11 6z"
+          fill="currentColor"
+          fill-opacity="0.9"
+        />
+      </svg>
+    </div>
+    
+  </div>
+  
+  <!--v-if-->
+  <!--v-if-->
+  
+</div>
+`;
+
+exports[`dropdown-menu > DropdownItem Props > : optionsLayout 2`] = `
+<div
+  class="t-dropdown-menu"
+>
+  <div
+    class="t-dropdown-menu__bar"
+  >
+    
+    <div
+      class="t-dropdown-menu__item t-is-active"
+    >
+      <div
+        class="t-dropdown-menu__title"
+      >
+        树形双列
+      </div>
+      <svg
+        class="t-icon t-icon-caret-down-small t-dropdown-menu__arrow"
+        fill="none"
+        height="1em"
+        viewBox="0 0 16 16"
+        width="1em"
+      >
+        <path
+          d="M11 6H5l3 4.5L11 6z"
+          fill="currentColor"
+          fill-opacity="0.9"
+        />
+      </svg>
+    </div>
+    <div
+      class="t-dropdown-menu__item"
+    >
+      <div
+        class="t-dropdown-menu__title"
+      >
+        树形三列
+      </div>
+      <svg
+        class="t-icon t-icon-caret-down-small t-dropdown-menu__arrow"
+        fill="none"
+        height="1em"
+        viewBox="0 0 16 16"
+        width="1em"
+      >
+        <path
+          d="M11 6H5l3 4.5L11 6z"
+          fill="currentColor"
+          fill-opacity="0.9"
+        />
+      </svg>
+    </div>
+    
+  </div>
+  
+  <div
+    class="t-dropdown-item"
+    style="top: 0px;"
+  >
+    <!-- &lt;t-overlay v-if="isShowItems && showOverlay" @click="onClickOverlay" /&gt; -->
+    <div
+      class="t-dropdown-item__content t-is-tree t-is-col2"
+      style="transition: transform 200ms ease;"
+    >
+      <div
+        class="t-dropdown-item__bd"
+      >
+        
+        
+        <!-- 树形列表 ST -->
+        
+        <div
+          class="t-dropdown-item__tree-group"
+        >
+          <div
+            class="t-radio-group"
+            role="radiogroup"
+          >
+            <!--v-if-->
+            
+            <!-- 树形列表 - 父级节点 ST -->
+            
+            <div
+              class="t-radio t-is-checked t-dropdown-item__radio t-is-checked"
+            >
+              <span
+                class="t-radio__content-wrap"
+              >
+                <!--v-if-->
+                <span
+                  class="t-radio__label-wrap"
+                >
+                  <span
+                    class="t-radio__content-title t-radio__content-right-title"
+                  >
+                    选项 1
+                  </span>
+                  <!--v-if-->
+                </span>
+                <span
+                  class="t-radio__icon-wrap t-radio__icon-right-wrap"
+                >
+                  <input
+                    class="t-radio__original-right"
+                    name=""
+                    type="radio"
+                    value="options_0"
+                  />
+                  <div
+                    class="t-radio__icon t-radio__icon--checked t-radio__icon--custom"
+                  >
+                    <!---->
+                  </div>
+                </span>
+              </span>
+              <!--下边框 -->
+              <!--v-if-->
+            </div>
+            <div
+              class="t-radio t-dropdown-item__radio"
+            >
+              <span
+                class="t-radio__content-wrap"
+              >
+                <!--v-if-->
+                <span
+                  class="t-radio__label-wrap"
+                >
+                  <span
+                    class="t-radio__content-title t-radio__content-right-title"
+                  >
+                    选项 2
+                  </span>
+                  <!--v-if-->
+                </span>
+                <span
+                  class="t-radio__icon-wrap t-radio__icon-right-wrap"
+                >
+                  <input
+                    class="t-radio__original-right"
+                    name=""
+                    type="radio"
+                    value="options_1"
+                  />
+                  <div
+                    class="t-radio__icon t-radio__icon--custom"
+                  >
+                    <!---->
+                  </div>
+                </span>
+              </span>
+              <!--下边框 -->
+              <!--v-if-->
+            </div>
+            <div
+              class="t-radio t-dropdown-item__radio"
+            >
+              <span
+                class="t-radio__content-wrap"
+              >
+                <!--v-if-->
+                <span
+                  class="t-radio__label-wrap"
+                >
+                  <span
+                    class="t-radio__content-title t-radio__content-right-title"
+                  >
+                    选项 3
+                  </span>
+                  <!--v-if-->
+                </span>
+                <span
+                  class="t-radio__icon-wrap t-radio__icon-right-wrap"
+                >
+                  <input
+                    class="t-radio__original-right"
+                    name=""
+                    type="radio"
+                    value="options_2"
+                  />
+                  <div
+                    class="t-radio__icon t-radio__icon--custom"
+                  >
+                    <!---->
+                  </div>
+                </span>
+              </span>
+              <!--下边框 -->
+              <!--v-if-->
+            </div>
+            
+            <!-- 树形列表 - 父级节点 ED -->
+            
+          </div>
+        </div>
+        <div
+          class="t-dropdown-item__tree-group"
+        >
+          
+          <!-- 树形列表 - 叶子节点 ST -->
+          
+          <!-- 树形列表 - 叶子节点（多选） ST -->
+          <div
+            class="t-checkbox-group"
+          >
+            
+            
+            <div
+              class="t-checkbox t-is-checked"
+            >
+              <div
+                class="t-checkbox__content-wrap"
+              >
+                <!--v-if-->
+                <span
+                  class="t-checkbox__label t-checkbox__label-left"
+                >
+                  <span
+                    style="display: -webkit-box; overflow: hidden;"
+                  >
+                    选项 1
+                  </span>
+                  <!--v-if-->
+                </span>
+                <span
+                  class="t-checkbox__icon-right"
+                >
+                  <input
+                    class="t-checkbox__original-right"
+                    name=""
+                    type="checkbox"
+                    value="options_0"
+                  />
+                  <svg
+                    class="t-icon t-icon-check-circle-filled"
+                    fill="none"
+                    height="1em"
+                    viewBox="0 0 16 16"
+                    width="1em"
+                  >
+                    <path
+                      d="M8 15A7 7 0 108 1a7 7 0 000 14zM4.5 8.2l.7-.7L7 9.3l3.8-3.8.7.7L7 10.7 4.5 8.2z"
+                      fill="currentColor"
+                      fill-opacity="0.9"
+                    />
+                  </svg>
+                </span>
+              </div>
+              <!--下边框 -->
+              <!--v-if-->
+            </div>
+            <div
+              class="t-checkbox t-is-checked"
+            >
+              <div
+                class="t-checkbox__content-wrap"
+              >
+                <!--v-if-->
+                <span
+                  class="t-checkbox__label t-checkbox__label-left"
+                >
+                  <span
+                    style="display: -webkit-box; overflow: hidden;"
+                  >
+                    选项 2
+                  </span>
+                  <!--v-if-->
+                </span>
+                <span
+                  class="t-checkbox__icon-right"
+                >
+                  <input
+                    class="t-checkbox__original-right"
+                    name=""
+                    type="checkbox"
+                    value="options_1"
+                  />
+                  <svg
+                    class="t-icon t-icon-check-circle-filled"
+                    fill="none"
+                    height="1em"
+                    viewBox="0 0 16 16"
+                    width="1em"
+                  >
+                    <path
+                      d="M8 15A7 7 0 108 1a7 7 0 000 14zM4.5 8.2l.7-.7L7 9.3l3.8-3.8.7.7L7 10.7 4.5 8.2z"
+                      fill="currentColor"
+                      fill-opacity="0.9"
+                    />
+                  </svg>
+                </span>
+              </div>
+              <!--下边框 -->
+              <!--v-if-->
+            </div>
+            
+            
+          </div>
+          <!-- 树形列表 - 叶子节点（多选） ED -->
+          
+          <!-- 树形列表 - 叶子节点 ED -->
+          
+        </div>
+        
+        <!-- 树形列表 ED -->
+        
+        
+      </div>
+      <div
+        class="t-dropdown-item__ft"
+      >
+        <button
+          aria-disabled="false"
+          class="t-button t-size-m t-button--outline t-button--default t-button--shape-rectangle"
+          role="button"
+        >
+          <!---->
+          <span
+            class="t-button__text"
+          >
+            
+            重置
+            
+          </span>
+        </button>
+        <button
+          aria-disabled="false"
+          class="t-button t-size-m t-button--base t-button--primary t-button--shape-rectangle"
+          role="button"
+        >
+          <!---->
+          <span
+            class="t-button__text"
+          >
+            
+            确定
+            
+          </span>
+        </button>
+      </div>
+    </div>
+  </div>
+  <!--v-if-->
+  
+</div>
+`;
+
+exports[`dropdown-menu > props > : duration 1`] = `
+<div
+  class="t-dropdown-menu"
+>
+  <div
+    class="t-dropdown-menu__bar t-dropdown-menu__bar t-dropdown-menu__bar--open"
+  >
+    
+    <div
+      class="t-dropdown-menu__item t-is-disabled"
+    >
+      <div
+        class="t-dropdown-menu__title"
+      >
+        菜单
+      </div>
+      <svg
+        class="t-icon t-icon-caret-down-small t-dropdown-menu__arrow"
+        fill="none"
+        height="1em"
+        viewBox="0 0 16 16"
+        width="1em"
+      >
+        <path
+          d="M11 6H5l3 4.5L11 6z"
+          fill="currentColor"
+          fill-opacity="0.9"
+        />
+      </svg>
+    </div>
+    <div
+      class="t-dropdown-menu__item t-is-active"
+    >
+      <div
+        class="t-dropdown-menu__title"
+      >
+        菜单
+      </div>
+      <svg
+        class="t-icon t-icon-caret-down-small t-dropdown-menu__arrow"
+        fill="none"
+        height="1em"
+        viewBox="0 0 16 16"
+        width="1em"
+      >
+        <path
+          d="M11 6H5l3 4.5L11 6z"
+          fill="currentColor"
+          fill-opacity="0.9"
+        />
+      </svg>
+    </div>
+    
+  </div>
+  
+  <!--v-if-->
+  <div
+    class="t-dropdown-item t-is-expanded"
+    style="top: 0px;"
+  >
+    <!-- &lt;t-overlay v-if="isShowItems && showOverlay" @click="onClickOverlay" /&gt; -->
+    <div
+      class="t-dropdown-item__content t-is-single"
+      style="transition: transform 500ms ease;"
+    >
+      <div
+        class="t-dropdown-item__bd"
+      >
+        
+        
+        
+        <!-- 单选列表 -->
+        <div
+          class="t-radio-group"
+          role="radiogroup"
+        >
+          <!--v-if-->
+          
+          
+          <div
+            class="t-radio t-dropdown-item__radio t-is-tick"
+          >
+            <span
+              class="t-radio__content-wrap"
+            >
+              <!--v-if-->
+              <span
+                class="t-radio__label-wrap"
+              >
+                <span
+                  class="t-radio__content-title t-radio__content-right-title"
+                >
+                  选项一
+                </span>
+                <!--v-if-->
+              </span>
+              <span
+                class="t-radio__icon-wrap t-radio__icon-right-wrap"
+              >
+                <input
+                  class="t-radio__original-right"
+                  name=""
+                  type="radio"
+                  value="option_0"
+                />
+                <div
+                  class="t-radio__icon t-radio__icon--custom"
+                >
+                  <!---->
+                </div>
+              </span>
+            </span>
+            <!--下边框 -->
+            <div
+              class="t-radio__border t-radio__border--right"
+            />
+          </div>
+          <div
+            class="t-radio t-dropdown-item__radio t-is-tick"
+          >
+            <span
+              class="t-radio__content-wrap"
+            >
+              <!--v-if-->
+              <span
+                class="t-radio__label-wrap"
+              >
+                <span
+                  class="t-radio__content-title t-radio__content-right-title"
+                >
+                  选项二
+                </span>
+                <!--v-if-->
+              </span>
+              <span
+                class="t-radio__icon-wrap t-radio__icon-right-wrap"
+              >
+                <input
+                  class="t-radio__original-right"
+                  name=""
+                  type="radio"
+                  value="option_1"
+                />
+                <div
+                  class="t-radio__icon t-radio__icon--custom"
+                >
+                  <!---->
+                </div>
+              </span>
+            </span>
+            <!--下边框 -->
+            <div
+              class="t-radio__border t-radio__border--right"
+            />
+          </div>
+          <div
+            class="t-radio t-is-disabled t-dropdown-item__radio t-is-tick"
+          >
+            <span
+              class="t-radio__content-wrap"
+            >
+              <!--v-if-->
+              <span
+                class="t-radio__label-wrap"
+              >
+                <span
+                  class="t-radio__content-title t-is-disabled t-radio__content-right-title"
+                >
+                  禁用选项
+                </span>
+                <!--v-if-->
+              </span>
+              <span
+                class="t-radio__icon-wrap t-radio__icon-right-wrap"
+              >
+                <input
+                  class="t-radio__original-right"
+                  disabled=""
+                  name=""
+                  type="radio"
+                  value="disabled-item"
+                />
+                <div
+                  class="t-radio__icon t-radio__icon--disabled t-radio__icon--custom"
+                >
+                  <!---->
+                </div>
+              </span>
+            </span>
+            <!--下边框 -->
+            <div
+              class="t-radio__border t-radio__border--right"
+            />
+          </div>
+          
+          
+        </div>
+        
+        
+        
+      </div>
+      <!--v-if-->
+    </div>
+  </div>
+  
+</div>
+`;
diff --git a/src/dropdown-menu/__test__/index.test.jsx b/src/dropdown-menu/__test__/index.test.jsx
new file mode 100644
index 00000000..0a24dabe
--- /dev/null
+++ b/src/dropdown-menu/__test__/index.test.jsx
@@ -0,0 +1,378 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi } from 'vitest';
+import { nextTick, ref } from 'vue';
+import {DropdownMenu, DropdownItem} from '../index';
+import RadioGroup from '../../radio-group';
+import Radio from '../../radio';
+import CheckBox, { CheckboxGroup } from '../../checkbox'
+import Button from '../../button';
+
+const sleep = (duration) =>
+  new Promise((resolve) =>
+    setTimeout(() => {
+      resolve();
+    }, duration),
+  );
+
+
+const prefix = 't'
+const name = `${prefix}-dropdown-menu`;
+
+const chineseMap = '一二';
+const emptyArr = new Array(2).fill(null);
+const options = emptyArr.map((_, i) => ({
+  title: `选项${chineseMap[i]}`,
+  value: `option_${i}`,
+  disabled: false,
+}));
+
+options.push({
+  title: '禁用选项',
+  value: 'disabled-item',
+  disabled: true,
+});
+
+
+describe('dropdown-menu', () => {
+  describe('props', () => {
+    // TODO：overlay 遮罩相关
+    it(': duration', async () => {
+      const value1 = ref('option_2');
+      const value2 = ref('option_2');
+      const items = [
+        {
+          value: value1,
+          label: "菜单",
+          disabled: true,
+          options: options
+        },
+        {
+          value: value2,
+          label: "菜单",
+          disabled: false,
+          options: options
+        }
+      ]
+      const duration = 500;
+      const onChange = vi.fn();
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu duration={duration}>
+              {{
+                default: items.map((item, index) => {
+                  return <DropdownItem v-model={item.value.value} label={item.label} disabled={item.disabled} options={item.options} onChange={onChange} />
+                })
+              }}
+            </DropdownMenu>
+          )
+        }
+      });
+
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+      $menuLabels.map(async (item, index) => {
+        expect(item.attributes('class').includes(`${prefix}-is-disabled`)).toEqual(items[index].disabled);
+        expect(item.text()).toEqual(items[index].label)
+        await item.trigger('click', { item, index })
+      });
+      // 动画延迟时间
+      await sleep(duration ?? 200);
+      expect(wrapper.element).toMatchSnapshot();
+      expect(wrapper.find(`.${prefix}-dropdown-item__content`).attributes('style').includes(`transition: transform ${duration}ms ease;`)).toBeTruthy();
+
+      // 此时，$menuLabels[1]为激活态，再次点击时收起
+      const index = 1;
+      const itemTemp = items[index];
+      expect($menuLabels[index].attributes('class').includes(`${prefix}-is-active`)).toBeTruthy();
+      $menuLabels[1].trigger('click', { itemTemp, index })
+      await sleep( duration?? 200);
+      expect($menuLabels[index].attributes('class').includes(`${prefix}-is-active`)).toBeFalsy();
+    })
+
+    it(': button', async () => {
+      // 底部 button 仅在 multiple/optionsLayout === 'tree'时，有效
+      const value1 = ref(['option_1']);
+      const items = [
+        {
+          value: value1,
+          label: "菜单",
+          multiple: true,
+          options: options
+        }
+      ]
+      const onChange = vi.fn()
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu>
+              {{
+                default: items.map((item, index) => {
+                  return <DropdownItem value={item.value.value} label={item.label} multiple={item.multiple} options={item.options} onChange={onChange} />
+                })
+              }}
+            </DropdownMenu>
+          )
+        }
+      });
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+      $menuLabels.map((item, index) => {
+        item.trigger('click', { item, index })
+      });
+      await sleep(200);
+
+      const $dropdownItems = wrapper.findAll(`.${prefix}-dropdown-item`);
+      expect($dropdownItems[0].attributes('class').includes(`${prefix}-is-expanded`)).toBeTruthy();
+      expect(wrapper.findAllComponents(Button).length).toEqual(2);
+
+      // 初始化，选中1项
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(1);
+      const $buttons = wrapper.findAll(`.${prefix}-button`);
+      // 重置, 清空,不触发 change, 选中项为 0
+      await $buttons[0].trigger('click');
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(0);
+
+      // 模拟点击，选中第1项
+      const $checkbox = wrapper.findAll(`.t-checkbox`);
+      await $checkbox[0].find(`.${prefix}-checkbox__original-left`).trigger('click');
+      // 确定, 触发 change, 返回选中值 [value]
+      await $buttons[1].trigger('click');
+      expect(onChange).toBeCalledTimes(1);
+      expect(onChange).toHaveBeenCalledWith(['option_0']);
+    });
+  });
+
+  describe('DropdownItem Props', () => {
+    it(': label', () => {
+      const value1 = ref('option_2');
+      const value2 = ref('option_2');
+      const items = [
+        {
+          value: value1,
+          label: "菜单",
+          disabled: true,
+          options: options
+        },
+        {
+          value: value2,
+          label: "菜单",
+          disabled: false,
+          options: options
+        }
+      ]
+
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu>
+              {{
+                default: items.map((item, index) => {
+                  return <DropdownItem value={item.value.value} label={item.label} disabled={item.disabled} options={item.options} />
+                })
+              }}
+            </DropdownMenu>
+          )
+        }
+      });
+
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+      $menuLabels.map(async (item, index) => {
+        expect(item.attributes('class').includes(`${prefix}-is-disabled`)).toEqual(items[index].disabled);
+        expect(item.text()).toEqual(items[index].label)
+        await item.trigger('click', { item, index })
+      });
+    })
+
+    it(': multiple', async () => {
+      const value1 = ref('option_1');
+      const value2 = ref(['option_1']);
+      const items = [
+        {
+          value: value1,
+          label: "菜单",
+          options: options,
+          multiple: false,
+        },
+        {
+          value: value2,
+          label: "菜单",
+          options: options,
+          multiple: true,
+        }
+      ]
+
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu>
+              {{
+                default: items.map((item, index) => {
+                  return <DropdownItem value={item.value.value} label={item.label} multiple={item.multiple} options={item.options} />
+                })
+              }}
+            </DropdownMenu>
+          )
+        }
+      });
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+
+      // multiple = false, 单选列表, t-radio-group, t-radio
+      const index0 = 0;
+      const item0 = items[0];
+      await $menuLabels[0].trigger('click', { item0, index0 })
+      expect(wrapper.findComponent(RadioGroup).exists()).toBeTruthy();
+      expect(wrapper.findAllComponents(Radio).length).toEqual(item0.options.length);
+
+      const $radios = wrapper.findAll(`.t-radio`);
+      // 模拟点击单选列表 Radio，重复点击已选中态不会触发 change 事件， 最终选中项为 1
+      await $radios[0].find(`.${prefix}-radio__content-title`).trigger('click');
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(1);
+
+      // multiple = true, 多选列表, t-checkbox-group, t-checkbox
+      const index1 = 1;
+      const item1 = items[1];
+      await $menuLabels[1].trigger('click', { item1, index1 })
+      expect(wrapper.findComponent(CheckboxGroup).exists()).toBeTruthy();
+      expect(wrapper.findAllComponents(CheckBox).length).toEqual(item1.options.length);
+      // 初始选中1项
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(1);
+
+      // 模拟点击多选列表 Checkbox，重复点击已选中态也会触发 change 事件， 最终选中项有多项
+      const $checkbox = wrapper.findAll(`.t-checkbox`);
+      // 点选1项，默认1项，共2项选中态
+      await $checkbox[0].find(`.${prefix}-checkbox__original-left`).trigger('click');
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(2);
+      // 点选1项，取消1项，共1项选中态
+      await $checkbox[1].find(`.${prefix}-checkbox__original-left`).trigger('click');
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(1);
+      // 禁用项，无法选中
+      await $checkbox[2].find(`.${prefix}-checkbox__original-left`).trigger('click');
+      expect(wrapper.findAll(`.${prefix}-is-checked`).length).toEqual(1);
+    })
+
+    it(': optionsColumns', async () => {
+      // options-columns值为[1,3]区间内，仅在 multiple = true 时，有效
+      const value1 = ref(['option_1']);
+      const items = [
+        {
+          value: value1,
+          label: "菜单",
+          options: options,
+          multiple: true,
+          optionsColumns: 2
+        }
+      ]
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu>
+              {{
+                default: items.map((item, index) => {
+                  return <DropdownItem value={item.value.value} label={item.label} multiple={item.multiple} options={item.options} optionsColumns={item.optionsColumns} />
+                })
+              }}
+            </DropdownMenu>
+          )
+        }
+      });
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+      $menuLabels.map((item, index) => {
+        item.trigger('click', { item, index })
+      });
+      await sleep(200);
+      expect(wrapper.find(`.${prefix}-dropdown-item__content`).attributes('class').includes(`${prefix}-is-col${items[0].optionsColumns}`)).toBeTruthy();
+    });
+
+    it(': optionsLayout', async () => {
+      const buildTree = (length, ...childLengths) => {
+        const tree = [];
+        for (let i = 0; i < length; i++) {
+          const item = {
+            title: `选项 ${i + 1}`,
+            value: `options_${i}`,
+          };
+          if (childLengths.length > 0) {
+            const [childLength, ...moreChildLenthes] = childLengths;
+            item.options = buildTree(childLength, ...moreChildLenthes);
+          }
+          tree.push(item);
+        }
+        return tree;
+      };
+      const radioLength = 3;
+      const checkboxLength = 2;
+
+      const optionsT1 = ref(buildTree(radioLength, checkboxLength)); // 树形列表 - 叶子节点（单选）
+      const optionsT2 = ref(buildTree(radioLength, radioLength, checkboxLength)); // 树形列表 - 叶子节点（多选）
+      const treeValue1 = ref(['options_0',  ['options_0', 'options_1']]);
+      const treeValue2 = ref(['options_0', 'options_2', ['options_0', 'options_1']]);
+
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu>
+              <DropdownItem v-model={treeValue1} label="树形双列" options={optionsT1.value} optionsLayout="tree" multiple={true} />
+              <DropdownItem v-model={treeValue2} label="树形三列" options={optionsT2.value} optionsLayout="tree" multiple={true} />
+            </DropdownMenu>
+          )
+        }
+      });
+
+      expect(wrapper.element).toMatchSnapshot();
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+      const index0 = 0;
+      await $menuLabels[0].trigger('click', { optionsT1, index0 })
+
+      // 初始化: Radio 有 radioLength 项, 选中 1 项; checkbox 有 checkboxLength 项, 选中 2 项
+      expect(wrapper.findAllComponents(Radio).length).toEqual(3);
+      expect(wrapper.findAllComponents(CheckBox).length).toEqual(2);
+
+      expect(wrapper.find(`.${prefix}-radio-group`).findAll(`.${prefix}-is-checked`).length).toEqual(1);
+      expect(wrapper.find(`.${prefix}-checkbox-group`).findAll(`.${prefix}-is-checked`).length).toEqual(2);
+
+      const $buttons = wrapper.findAll(`.${prefix}-button`);
+      // 重置, 清空,不触发 change, 选中项为 0
+      await $buttons[0].trigger('click');
+      expect(wrapper.find(`.${prefix}-radio-group`).findAll(`.${prefix}-is-checked`).length).toEqual(0);
+      expect(wrapper.find(`.${prefix}-checkbox-group`).findAll(`.${prefix}-is-checked`).length).toEqual(0);
+    });
+
+    it(': onChange', async () => {
+      const value1 = ref(['option_1']);
+      const items = [
+        {
+          value: value1,
+          label: "菜单",
+          options: options,
+        }
+      ]
+      const onChange = vi.fn();
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <DropdownMenu>
+              {{
+                default: items.map((item, index) => {
+                  return <DropdownItem value={item.value.value} label={item.label} options={item.options} onChange={onChange} />
+                })
+              }}
+            </DropdownMenu>
+          )
+        }
+      });
+      const $menuLabels = wrapper.findAll(`.${name}__item`);
+      $menuLabels.map((item, index) => {
+        item.trigger('click', { item, index })
+      });
+      await sleep(200);
+      const $radios = wrapper.findAll(`.t-radio`);
+      await $radios[0].find(`.${prefix}-radio__content-title`).trigger('click');
+      expect(onChange).toHaveBeenCalledTimes(1);
+      // 单选列表，原本为选中态的，再次点击时，不会触发 change
+      await $radios[1].find(`.${prefix}-radio__content-title`).trigger('click');
+      expect(onChange).toHaveBeenCalledTimes(1);
+      // 禁用态按钮，不触发 change
+      await $radios[2].find(`.${prefix}-radio__content-title`).trigger('click');
+      expect(onChange).toHaveBeenCalledTimes(1);
+    });
+  });
+})
diff --git a/src/grid/__test__/__snapshots__/demo.test.jsx.snap b/src/grid/__test__/__snapshots__/demo.test.jsx.snap
index 17bd8acd..bcdfabe9 100644
--- a/src/grid/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/grid/__test__/__snapshots__/demo.test.jsx.snap
@@ -1,13 +1,18 @@
 // Vitest Snapshot v1
 
 exports[`Grid > Grid badgeVue demo works fine 1`] = `
-<div>
+<div
+  class="grid-demo"
+  data-v-26858cd4=""
+>
   <div
     class="t-grid"
+    data-v-26858cd4=""
   >
     
     <div
       class="t-grid-item"
+      data-v-26858cd4=""
       style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
     >
       <div
@@ -51,6 +56,7 @@ exports[`Grid > Grid badgeVue demo works fine 1`] = `
     </div>
     <div
       class="t-grid-item"
+      data-v-26858cd4=""
       style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
     >
       <div
@@ -94,6 +100,7 @@ exports[`Grid > Grid badgeVue demo works fine 1`] = `
     </div>
     <div
       class="t-grid-item"
+      data-v-26858cd4=""
       style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
     >
       <div
@@ -137,6 +144,7 @@ exports[`Grid > Grid badgeVue demo works fine 1`] = `
     </div>
     <div
       class="t-grid-item"
+      data-v-26858cd4=""
       style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
     >
       <div
@@ -148,7 +156,7 @@ exports[`Grid > Grid badgeVue demo works fine 1`] = `
           <div
             class="t-badge__inner t-badge--has-children t-badge--medium t-badge--circle"
           >
-            ...
+            ···
           </div>
           
           
@@ -320,7 +328,7 @@ exports[`Grid > Grid baseVue demo works fine 1`] = `
             class="t-grid-item__title"
             style="padding-top: 8px; margin-bottom: 4px;"
           >
-            最多四字
+            标题五字内
           </div>
           <div
             class="t-grid-item__description"
@@ -802,132 +810,219 @@ exports[`Grid > Grid baseVue demo works fine 1`] = `
 `;
 
 exports[`Grid > Grid descVue demo works fine 1`] = `
-<div>
+<div
+  data-v-app=""
+>
+  
   <div
-    class="t-grid"
+    class="grid-demo"
+    data-v-f7d95d20=""
   >
-    
     <div
-      class="t-grid-item"
-      style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
+      class="t-grid"
+      data-v-f7d95d20=""
     >
+      
       <div
-        class="t-grid-item__image-box"
-      >
-        
-        <img
-          class="t-grid-item__image"
-          src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
-          style="width: 48px; height: 48px;"
-        />
-        
-      </div>
-      <div
-        class="t-grid-item__text"
-        style="padding-left: 0px;"
+        class="t-grid-item"
+        data-v-f7d95d20=""
+        style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
       >
         <div
-          class="t-grid-item__title"
-          style="padding-top: 8px; margin-bottom: 4px;"
+          class="t-grid-item__image-box"
         >
-          标题文字
+          
+          <img
+            class="t-grid-item__image"
+            src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
+            style="width: 48px; height: 48px;"
+          />
+          
         </div>
         <div
-          class="t-grid-item__description"
+          class="t-grid-item__text"
+          style="padding-left: 0px;"
         >
-          说明文字
+          <div
+            class="t-grid-item__title"
+            style="padding-top: 8px; margin-bottom: 4px;"
+          >
+            标题文字
+          </div>
+          <div
+            class="t-grid-item__description"
+          >
+            说明文字
+          </div>
         </div>
       </div>
-    </div>
-    <div
-      class="t-grid-item"
-      style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
-    >
       <div
-        class="t-grid-item__image-box"
+        class="t-grid-item"
+        data-v-f7d95d20=""
+        style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
       >
-        
-        <img
-          class="t-grid-item__image"
-          src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
-          style="width: 48px; height: 48px;"
-        />
-        
+        <div
+          class="t-grid-item__image-box"
+        >
+          
+          <img
+            class="t-grid-item__image"
+            src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
+            style="width: 48px; height: 48px;"
+          />
+          
+        </div>
+        <div
+          class="t-grid-item__text"
+          style="padding-left: 0px;"
+        >
+          <div
+            class="t-grid-item__title"
+            style="padding-top: 8px; margin-bottom: 4px;"
+          >
+            标题文字
+          </div>
+          <div
+            class="t-grid-item__description"
+          >
+            说明文字
+          </div>
+        </div>
       </div>
       <div
-        class="t-grid-item__text"
-        style="padding-left: 0px;"
+        class="t-grid-item"
+        data-v-f7d95d20=""
+        style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
       >
         <div
-          class="t-grid-item__title"
-          style="padding-top: 8px; margin-bottom: 4px;"
+          class="t-grid-item__image-box"
         >
-          标题文字
+          
+          <img
+            class="t-grid-item__image"
+            src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
+            style="width: 48px; height: 48px;"
+          />
+          
         </div>
         <div
-          class="t-grid-item__description"
+          class="t-grid-item__text"
+          style="padding-left: 0px;"
         >
-          说明文字
+          <div
+            class="t-grid-item__title"
+            style="padding-top: 8px; margin-bottom: 4px;"
+          >
+            标题最多六字
+          </div>
+          <div
+            class="t-grid-item__description"
+          >
+            说明最多六字
+          </div>
         </div>
       </div>
+      
     </div>
+  </div>
+  <div
+    class="grid-demo"
+    data-v-f7d95d20=""
+  >
     <div
-      class="t-grid-item"
-      style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
+      class="t-grid"
+      data-v-f7d95d20=""
     >
+      
       <div
-        class="t-grid-item__image-box"
+        class="t-grid-item"
+        data-v-f7d95d20=""
+        style="flex-basis: 50%; flex-direction: row; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
       >
-        
-        <img
-          class="t-grid-item__image"
-          src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
-          style="width: 48px; height: 48px;"
-        />
-        
+        <div
+          class="t-grid-item__image-box"
+        >
+          
+          <img
+            class="t-grid-item__image"
+            src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
+            style="width: 48px; height: 48px;"
+          />
+          
+        </div>
+        <div
+          class="t-grid-item__text"
+          style="padding-left: 12px;"
+        >
+          <div
+            class="t-grid-item__title"
+            style="padding-top: 0px; margin-bottom: 4px;"
+          >
+            标题文字
+          </div>
+          <div
+            class="t-grid-item__description"
+          >
+            说明文字
+          </div>
+        </div>
       </div>
       <div
-        class="t-grid-item__text"
-        style="padding-left: 0px;"
+        class="t-grid-item"
+        data-v-f7d95d20=""
+        style="flex-basis: 50%; flex-direction: row; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
       >
         <div
-          class="t-grid-item__title"
-          style="padding-top: 8px; margin-bottom: 4px;"
+          class="t-grid-item__image-box"
         >
-          标题最多六字
+          
+          <img
+            class="t-grid-item__image"
+            src="https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png"
+            style="width: 48px; height: 48px;"
+          />
+          
         </div>
         <div
-          class="t-grid-item__description"
+          class="t-grid-item__text"
+          style="padding-left: 12px;"
         >
-          说明最多六字
+          <div
+            class="t-grid-item__title"
+            style="padding-top: 0px; margin-bottom: 4px;"
+          >
+            标题文字
+          </div>
+          <div
+            class="t-grid-item__description"
+          >
+            说明文字
+          </div>
         </div>
       </div>
+      
     </div>
-    
   </div>
+  
 </div>
 `;
 
 exports[`Grid > Grid mobileVue demo works fine 1`] = `
 <div
   class="tdesign-mobile-demo"
-  data-v-2417457e=""
 >
   <h1
     class="title"
-    data-v-2417457e=""
   >
     Grid 宫格
   </h1>
   <p
     class="summary"
-    data-v-2417457e=""
   >
     用于功能入口布局，将页面或特定区域切分成若干等大的区块，形成若干功能入口。
   </p>
   <div
     class="tdesign-mobile-demo-block"
-    data-v-2417457e=""
   >
     <h2
       class="tdesign-mobile-demo-block__title"
@@ -940,18 +1035,19 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
        
     </p>
     
+    
     <div
       class="grid-demo"
-      data-v-2417457e=""
+      data-v-3e578ca0=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-3e578ca0=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -984,7 +1080,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1017,7 +1113,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1050,7 +1146,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1086,16 +1182,16 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
     </div>
     <div
       class="grid-demo"
-      data-v-2417457e=""
+      data-v-3e578ca0=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-3e578ca0=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1128,7 +1224,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1161,7 +1257,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1194,7 +1290,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1227,7 +1323,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1263,16 +1359,16 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
     </div>
     <div
       class="grid-demo"
-      data-v-2417457e=""
+      data-v-3e578ca0=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-3e578ca0=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1305,7 +1401,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1338,7 +1434,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1374,16 +1470,16 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
     </div>
     <div
       class="grid-demo short-width"
-      data-v-2417457e=""
+      data-v-3e578ca0=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-3e578ca0=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1416,7 +1512,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1449,7 +1545,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1482,7 +1578,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1515,7 +1611,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-3e578ca0=""
           style="flex-basis: 20%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1550,10 +1646,10 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
       </div>
     </div>
     
+    
   </div>
   <div
     class="tdesign-mobile-demo-block"
-    data-v-2417457e=""
   >
     <h2
       class="tdesign-mobile-demo-block__title"
@@ -1566,18 +1662,19 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
        
     </p>
     
+    
     <div
       class="grid-demo"
-      data-v-2417457e=""
+      data-v-f7d95d20=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-f7d95d20=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-f7d95d20=""
           style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1610,7 +1707,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-f7d95d20=""
           style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1643,7 +1740,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-f7d95d20=""
           style="flex-basis: 33.333333333333336%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1679,16 +1776,16 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
     </div>
     <div
       class="grid-demo"
-      data-v-2417457e=""
+      data-v-f7d95d20=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-f7d95d20=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-f7d95d20=""
           style="flex-basis: 50%; flex-direction: row; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1721,7 +1818,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-f7d95d20=""
           style="flex-basis: 50%; flex-direction: row; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1756,10 +1853,10 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
       </div>
     </div>
     
+    
   </div>
   <div
     class="tdesign-mobile-demo-block"
-    data-v-2417457e=""
   >
     <h2
       class="tdesign-mobile-demo-block__title"
@@ -1774,16 +1871,16 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
     
     <div
       class="grid-demo"
-      data-v-2417457e=""
+      data-v-26858cd4=""
     >
       <div
         class="t-grid"
-        data-v-2417457e=""
+        data-v-26858cd4=""
       >
         
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-26858cd4=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1827,7 +1924,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-26858cd4=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1871,7 +1968,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-26858cd4=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
@@ -1915,7 +2012,7 @@ exports[`Grid > Grid mobileVue demo works fine 1`] = `
         </div>
         <div
           class="t-grid-item"
-          data-v-2417457e=""
+          data-v-26858cd4=""
           style="flex-basis: 25%; flex-direction: column; padding-left: 0px; padding-right: 0px; align-items: center; justify-content: center; text-align: center;"
         >
           <div
diff --git a/src/grid/__test__/index.test.jsx b/src/grid/__test__/index.test.jsx
new file mode 100644
index 00000000..4fa96c89
--- /dev/null
+++ b/src/grid/__test__/index.test.jsx
@@ -0,0 +1,172 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Grid from '../grid.vue';
+import GridItem from '../grid-item.vue';
+import Badge from '../../badge/badge.vue';
+
+const prefix = 't'
+const name = `${prefix}-grid`;
+const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png';
+const items = [
+  {
+    text: "标题",
+    image: imgUrl
+  },
+  {
+    text: "标题",
+    image: imgUrl
+  }
+];
+const items2 = [
+  {
+    text: "标题",
+    description: "内容 description",
+    image: imgUrl
+  },
+  {
+    text: "标题",
+    description: "内容 description",
+    image: imgUrl
+  }
+];
+describe('grid', () => {
+  describe('props', () => {
+    it(': align ', async () => {
+      const align = 'left';
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Grid align={align}>
+              {{
+                default: items.map((item, index) => {
+                  return <GridItem text={item.text + index} image={item.image} />
+                })
+              }}
+            </Grid>
+          )
+        }
+      });
+      expect(wrapper.findComponent(Grid).exists()).toBeTruthy();
+      expect(wrapper.findAllComponents(GridItem).length).toEqual(items.length);
+      const $grid = wrapper.find(`.${name}`);
+      const $gridItems = wrapper.findAll(`.${name}-item`);
+      // align = 'left (自定义)
+      // border = false, column = 4, gutter = 0 (默认)
+      $gridItems.map(item => {
+        expect(item.attributes('class').includes(`${name}-item--bordered`)).toBeFalsy(); // border
+        expect(item.element.style.textAlign).toBe('left') // align
+        expect(item.element.style.flexBasis).toBe(`${100 / 4}%`); // column
+        expect(item.element.style.paddingLeft).toBe(`${0}px`) // gutter
+        expect(item.element.style.paddingRight).toBe(`${0}px`) // gutter
+      })
+    })
+
+    it(': border ', async () => {
+      const border = {
+        color: 'red',
+        width: '2px',
+        style: 'dashed'
+      };
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Grid border={border}>
+              {{
+                default: items.map((item, index) => {
+                  return <GridItem text={item.text + index} image={item.image} />
+                })
+              }}
+            </Grid>
+          )
+        }
+      });
+      const $gridItems = wrapper.findAll(`.${name}-item`);
+      // border: object
+      $gridItems.map(item => {
+        expect(item.attributes('class').includes(`${name}-item--bordered`)).toBeTruthy(); // border
+        expect(item.element.style.borderColor).toBe(border.color)
+        expect(item.element.style.borderWidth).toBe(border.width);
+      })
+    })
+
+    it(': column && gutter ', async () => {
+      const column = 5;
+      const gutter = 10;
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Grid column={column} gutter={gutter}>
+              {{
+                default: items.map((item, index) => {
+                  return <GridItem text={item.text + index} image={item.image} />
+                })
+              }}
+            </Grid>
+          )
+        }
+      });
+      expect(wrapper.findComponent(Grid).exists()).toBeTruthy();
+      expect(wrapper.findAllComponents(GridItem).length).toEqual(items.length);
+      const $grid = wrapper.find(`.${name}`);
+      const $gridItems = wrapper.findAll(`.${name}-item`);
+      // column = 4, gutter = 10 (自定义)
+      $gridItems.map(item => {
+        expect(item.element.style.textAlign).toBe('center') // align 默认为 center
+        expect(item.element.style.flexBasis).toBe(`${100 / column}%`); // column
+        expect(item.element.style.paddingLeft).toBe(`${gutter}px`) // gutter
+        expect(item.element.style.paddingRight).toBe(`${gutter}px`) // gutter
+      })
+    })
+  })
+});
+
+describe('grid-item', () => {
+  describe('props', () => {
+    it(': text && image && description', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Grid>
+              {{
+                default: items2.map((item, index) => {
+                  return <GridItem text={item.text + index} description={item.description + index} image={item.image} />
+                })
+              }}
+            </Grid>
+          )
+        }
+      });
+
+      const $gridItems = wrapper.findAll(`.${name}-item`);
+      $gridItems.map((item, index) => {
+        expect(item.find(`.${name}-item__title`).text()).toEqual(items2[index].text + index);
+        expect(item.find(`.${name}-item__description`).text()).toEqual(items2[index].description + index);
+        const $img = item.find(`.${name}-item__image-box img`);
+        expect($img.exists()).toBeTruthy();
+        expect($img.attributes('src')).toBe(imgUrl);
+      })
+    })
+  })
+
+  it(': badgeProps', async () => {
+    const wrapper = mount(() => (
+      <Grid>
+        <GridItem badgeProps={{ count: 1 }}></GridItem>
+      </Grid>
+    ));
+    const badge = wrapper.find('.t-badge__inner ');
+    expect(wrapper.findComponent(Badge).exists()).toBeTruthy();
+    expect(badge.exists()).toBeTruthy();
+    expect(badge.text()).toBe('1');
+  });
+
+  it(': layout', async () => {
+    const wrapper = mount(() => (
+      <Grid>
+        <GridItem text="标题文字" image={imgUrl} description="描述" layout="horizontal"></GridItem>
+      </Grid>
+    ));
+    const item = wrapper.find('.t-grid-item');
+    expect(item.element.style.flexDirection).toBe('row');
+  });
+});
diff --git a/src/image-viewer/__test__/__snapshots__/demo.test.jsx.snap b/src/image-viewer/__test__/__snapshots__/demo.test.jsx.snap
index 32e33554..5f606105 100644
--- a/src/image-viewer/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/image-viewer/__test__/__snapshots__/demo.test.jsx.snap
@@ -46,8 +46,12 @@ exports[`ImageViewer > ImageViewer backgroundVue demo works fine 1`] = `
             </span>
           </button>
           <transition-stub
+            appear="false"
             background-color="rgba(0, 0, 0, 1)"
             class="t-image-viewer"
+            css="true"
+            name="t-overlay"
+            persisted="true"
           >
             <div
               class="t-overlay"
@@ -113,7 +117,11 @@ exports[`ImageViewer > ImageViewer baseVue demo works fine 1`] = `
             </span>
           </button>
           <transition-stub
+            appear="false"
             class="t-image-viewer"
+            css="true"
+            name="t-overlay"
+            persisted="true"
           >
             <div
               class="t-overlay"
@@ -179,7 +187,11 @@ exports[`ImageViewer > ImageViewer closeBtnVue demo works fine 1`] = `
             </span>
           </button>
           <transition-stub
+            appear="false"
             class="t-image-viewer"
+            css="true"
+            name="t-overlay"
+            persisted="true"
           >
             <div
               class="t-overlay"
@@ -245,7 +257,11 @@ exports[`ImageViewer > ImageViewer closeEventVue demo works fine 1`] = `
             </span>
           </button>
           <transition-stub
+            appear="false"
             class="t-image-viewer"
+            css="true"
+            name="t-overlay"
+            persisted="true"
           >
             <div
               class="t-overlay"
@@ -311,7 +327,11 @@ exports[`ImageViewer > ImageViewer imageListVue demo works fine 1`] = `
             </span>
           </button>
           <transition-stub
+            appear="false"
             class="t-image-viewer"
+            css="true"
+            name="t-overlay"
+            persisted="true"
           >
             <div
               class="t-overlay"
@@ -377,7 +397,11 @@ exports[`ImageViewer > ImageViewer initialIndexVue demo works fine 1`] = `
             </span>
           </button>
           <transition-stub
+            appear="false"
             class="t-image-viewer"
+            css="true"
+            name="t-overlay"
+            persisted="true"
           >
             <div
               class="t-overlay"
@@ -482,8 +506,12 @@ exports[`ImageViewer > ImageViewer mobileVue demo works fine 1`] = `
     
   </div>
   <transition-stub
+    appear="false"
     class="t-image-viewer"
+    css="true"
     data-v-ef8302fa=""
+    name="t-overlay"
+    persisted="true"
   >
     <div
       class="t-overlay"
@@ -494,8 +522,12 @@ exports[`ImageViewer > ImageViewer mobileVue demo works fine 1`] = `
     </div>
   </transition-stub>
   <transition-stub
+    appear="false"
     class="t-image-viewer"
+    css="true"
     data-v-ef8302fa=""
+    name="t-overlay"
+    persisted="true"
   >
     <div
       class="t-overlay"
@@ -506,8 +538,12 @@ exports[`ImageViewer > ImageViewer mobileVue demo works fine 1`] = `
     </div>
   </transition-stub>
   <transition-stub
+    appear="false"
     class="t-image-viewer"
+    css="true"
     data-v-ef8302fa=""
+    name="t-overlay"
+    persisted="true"
   >
     <div
       class="t-overlay"
diff --git a/src/image-viewer/__test__/__snapshots__/index.test.jsx.snap b/src/image-viewer/__test__/__snapshots__/index.test.jsx.snap
index e0c64b07..cf0b4e77 100644
--- a/src/image-viewer/__test__/__snapshots__/index.test.jsx.snap
+++ b/src/image-viewer/__test__/__snapshots__/index.test.jsx.snap
@@ -2,7 +2,11 @@
 
 exports[`ImageViewer > function > : onIndexChange 1`] = `
 <transition-stub
+  appear="false"
   class="t-image-viewer"
+  css="true"
+  name="t-overlay"
+  persisted="true"
 >
   <div
     class="t-overlay t-overlay--active"
diff --git a/src/image/__test__/index.test.jsx b/src/image/__test__/index.test.jsx
new file mode 100644
index 00000000..47907ee7
--- /dev/null
+++ b/src/image/__test__/index.test.jsx
@@ -0,0 +1,86 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Image from '../image.vue';
+import { LoadingIcon } from 'tdesign-icons-vue-next';
+import { nextTick } from 'vue';
+import { vi } from 'vitest';
+
+const IMAGE = 'https://tdesign.gtimg.com/site/upload1.png';
+const FAIL_IMAGE = 'https://123.jpg';
+const fitList = ['fill', 'contain', 'cover', 'none', 'scale-down'];
+const shapeList = ['circle', 'round', 'square'];
+const positionList = ['top', 'bottom', 'center', 'left', 'right'];
+
+describe('Image.vue', () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Image src={IMAGE} />);
+    const img = wrapper.find('.t-image__img');
+    expect(wrapper.classes()).toContain('t-image');
+    expect(img.exists()).toBeTruthy();
+    expect(img.attributes('src')).toBe(IMAGE);
+  });
+
+  it('alt render', async () => {
+    const wrapper = mount(() => <Image src={IMAGE} alt="图片" />);
+    const img = wrapper.find('.t-image__img');
+    expect(wrapper.classes()).toContain('t-image');
+    expect(img.attributes('alt')).toBe('图片');
+  });
+
+  it('fit render', async () => {
+    fitList.forEach((fit) => {
+      const wrapper = mount(() => <Image src={IMAGE} fit={fit} />);
+      const img = wrapper.find('.t-image__img');
+      expect(img.attributes('style')).toContain(`object-fit: ${fit}`);
+    });
+  });
+
+  it('shape render', async () => {
+    shapeList.forEach((shape) => {
+      const wrapper = mount(() => <Image src={IMAGE} shape={shape} />);
+      expect(wrapper.classes()).toContain(`t-image--${shape}`);
+    });
+  });
+
+  it('position render', async () => {
+    positionList.forEach((position) => {
+      const wrapper = mount(() => <Image src={IMAGE} position={position} />);
+      const img = wrapper.find('.t-image__img');
+      expect(img.attributes('style')).toContain(`object-position: ${position}`);
+    });
+  });
+
+  it('loading render', async () => {
+    const slots = {
+      loading: () => <LoadingIcon />,
+    };
+    const wrapper = mount(() => <Image src={IMAGE} v-slots={slots} />);
+    const status = wrapper.find('.t-image__status');
+    expect(status.exists()).toBeTruthy();
+    expect(wrapper.findComponent(LoadingIcon).exists()).toBeTruthy();
+  });
+
+  it('error render', async () => {
+    const onError = vi.fn();
+    const slots = {
+      error: () => '加载失败',
+    };
+    const wrapper = mount(() => <Image src={FAIL_IMAGE} v-slots={slots} onError={onError} />);
+    const img = wrapper.find('.t-image__img');
+    const status = wrapper.find('.t-image__status');
+    await nextTick();
+    await img.trigger('error');
+    expect(status.exists()).toBeTruthy();
+    // expect(status.text()).toBe('加载失败');
+    // expect(onError).toBeCalled();
+  });
+
+  it('load render', async () => {
+    const onLoad = vi.fn();
+    const wrapper = mount(() => <Image src={IMAGE} onLoad={onLoad} />);
+    const img = wrapper.find('.t-image__img');
+    await nextTick();
+    img.trigger('load');
+    // expect(onLoad).toBeCalled();
+  });
+});
diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx
new file mode 100644
index 00000000..9acd170c
--- /dev/null
+++ b/src/input/__test__/index.test.jsx
@@ -0,0 +1,153 @@
+import { nextTick, ref } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi } from 'vitest';
+import Input from '../input.vue';
+import { CloseCircleFilledIcon, InfoCircleFilledIcon, AppIcon } from 'tdesign-icons-vue-next';
+
+const alignList = ['center', 'right'];
+
+describe('Input.vue', async () => {
+  it('create', async () => {
+    const value = ref('123');
+    const wrapper = mount(<Input v-model={value.value} />);
+    expect(wrapper.classes()).toContain('t-input');
+    expect(wrapper.classes()).toContain('t-cell');
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.exists()).toBeTruthy();
+    expect(input.element.value).toBe('123');
+  });
+
+  it('placeholder render', async () => {
+    const wrapper = mount(<Input placeholder="请输入" />);
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.attributes('placeholder')).toBe('请输入');
+  });
+
+  it('label render', async () => {
+    const wrapper = mount(<Input label="标题" />);
+    const label = wrapper.find('.t-cell__title .t-input--label');
+    expect(label.exists()).toBeTruthy();
+    expect(label.text()).toBe('标题');
+  });
+
+  it('required render', async () => {
+    const wrapper = mount(<Input label="标题" required />);
+    const required = wrapper.find('.t-cell__title .t-cell--required');
+    expect(required.exists()).toBeTruthy();
+    expect(required.text()).toContain('*');
+  });
+
+  it('errorMessage render', async () => {
+    const wrapper = mount(<Input label="标题" errorMessage="错误信息" />);
+    const errorMessage = wrapper.find('.t-input__error-msg');
+    expect(errorMessage.exists()).toBeTruthy();
+    expect(errorMessage.text()).toBe('错误信息');
+  });
+
+  it('align render', async () => {
+    alignList.forEach((a) => {
+      const wrapper = mount(<Input label="标题" align={a} />);
+      const control = wrapper.find(`.t-input__control--${a}`);
+      expect(control.exists()).toBeTruthy();
+    });
+  });
+
+  it('clearable render', async () => {
+    const value = ref('123');
+    const handleClear = vi.fn();
+    const wrapper = mount(<Input label="标题" v-model={value.value} clearable onClear={handleClear} />);
+    const closeIcon = wrapper.findComponent(CloseCircleFilledIcon);
+    expect(closeIcon.exists()).toBeTruthy();
+    await closeIcon.trigger('click');
+    expect(value.value).toBe('');
+    expect(handleClear).toBeCalled();
+  });
+
+  it('disabled render', async () => {
+    const value = ref('123');
+    const wrapper = mount(<Input label="标题" v-model={value.value} disabled />);
+    const label = wrapper.find('.t-input--label');
+    expect(label.classes()).toContain('t-is-disabled');
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.element.hasAttribute('disabled')).toBeTruthy();
+  });
+
+  it('readonly render', async () => {
+    const value = ref('123');
+    const wrapper = mount(<Input label="标题" v-model={value.value} readonly />);
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.element.hasAttribute('readonly')).toBeTruthy();
+  });
+
+  it('maxlength render', async () => {
+    const value = ref('');
+    const wrapper = mount(<Input label="标题" v-model={value.value} maxlength={10} />);
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.element.getAttribute('maxlength')).toBe('10');
+  });
+
+  it('autocomplete render', async () => {
+    const wrapper = mount(<Input label="标题" autocomplete />);
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.element.getAttribute('autocomplete')).toBe('On');
+  });
+
+  it('size render', async () => {
+    const wrapper = mount(() => <Input label="标题" size="small" />);
+    expect(wrapper.classes()).toContain('t-input--size-small');
+  });
+
+  it('icon slot render', async () => {
+    const slots = {
+      prefixIcon: () => <InfoCircleFilledIcon />,
+      suffixIcon: () => <AppIcon />,
+    };
+    const wrapper = mount(<Input label="标题" v-slots={slots} />);
+    const leftIcon = wrapper.find('.t-cell__left-icon svg');
+    const rightIcon = wrapper.find('.t-cell__right-icon svg');
+    expect(wrapper.findComponent(InfoCircleFilledIcon).exists()).toBeTruthy();
+    expect(wrapper.findComponent(AppIcon).exists()).toBeTruthy();
+    expect(leftIcon.exists()).toBeTruthy();
+    expect(rightIcon.exists()).toBeTruthy();
+  });
+
+  it('type render', async () => {
+    const wrapper = mount(<Input label="标题" type="number" />);
+    const input = wrapper.find('.t-input__wrap input');
+    expect(input.element.getAttribute('type')).toBe('number');
+  });
+
+  it('blur trigger', async () => {
+    const onBlur = vi.fn();
+    const wrapper = mount(<Input label="标题" onBlur={onBlur} />);
+    const input = wrapper.find('.t-input__wrap input');
+    await input.trigger('blur');
+    expect(onBlur).toBeCalled();
+  });
+
+  it('focus trigger', async () => {
+    const onFocus = vi.fn();
+    const wrapper = mount(<Input label="标题" onFocus={onFocus} />);
+    const input = wrapper.find('.t-input__wrap input');
+    await input.trigger('focus');
+    expect(onFocus).toBeCalled();
+  });
+
+  it('change trigger', async () => {
+    const data = ref('');
+    const value = ref('');
+    const handleChange = (val) => {
+      value.value = val;
+    };
+    const wrapper = mount(<Input label="标题" v-model={data.value} onChange={handleChange} />);
+    const el = wrapper.find('.t-input__wrap input').element;
+    await nextTick();
+    const simulateEvent = (text, event) => {
+      el.value = text;
+      el.dispatchEvent(new Event(event));
+    };
+    simulateEvent('2', 'input');
+    await nextTick();
+    expect(value.value).toBe('2');
+  });
+});
diff --git a/src/message/__test__/__snapshots__/demo.test.jsx.snap b/src/message/__test__/__snapshots__/demo.test.jsx.snap
index 4ab2861c..b46ae126 100644
--- a/src/message/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/message/__test__/__snapshots__/demo.test.jsx.snap
@@ -23,7 +23,12 @@ exports[`Message > Message alignVue demo works fine 1`] = `
       </span>
     </button>
   </div>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
   
@@ -67,10 +72,20 @@ exports[`Message > Message closeVue demo works fine 1`] = `
       </span>
     </button>
   </div>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
   
@@ -100,7 +115,12 @@ exports[`Message > Message durationVue demo works fine 1`] = `
       </span>
     </button>
   </div>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
   
@@ -341,28 +361,68 @@ exports[`Message > Message mobileVue demo works fine 1`] = `
     </div>
     
   </div>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
 </div>
@@ -391,7 +451,11 @@ exports[`Message > Message offsetVue demo works fine 1`] = `
     </span>
   </button>
   <transition-stub
+    appear="false"
+    css="true"
+    name="message"
     offset="400,100,100"
+    persisted="false"
   >
     <!--v-if-->
   </transition-stub>
@@ -485,22 +549,38 @@ exports[`Message > Message textVue demo works fine 1`] = `
     <!--v-if-->
   </div>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-9078f4e6=""
+    name="message"
+    persisted="false"
   >
     <!--v-if-->
   </transition-stub>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-9078f4e6=""
+    name="message"
+    persisted="false"
   >
     <!--v-if-->
   </transition-stub>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-9078f4e6=""
+    name="message"
+    persisted="false"
   >
     <!--v-if-->
   </transition-stub>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-9078f4e6=""
+    name="message"
+    persisted="false"
   >
     <!--v-if-->
   </transition-stub>
@@ -576,16 +656,36 @@ exports[`Message > Message themeVue demo works fine 1`] = `
       </span>
     </button>
   </div>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
-  <transition-stub>
+  <transition-stub
+    appear="false"
+    css="true"
+    name="message"
+    persisted="false"
+  >
     <!--v-if-->
   </transition-stub>
   
diff --git a/src/overlay/__test__/__snapshots__/demo.test.jsx.snap b/src/overlay/__test__/__snapshots__/demo.test.jsx.snap
index 3e3640ac..23e6ec3a 100644
--- a/src/overlay/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/overlay/__test__/__snapshots__/demo.test.jsx.snap
@@ -36,7 +36,11 @@ exports[`Overlay > Overlay baseVue demo works fine 1`] = `
     </span>
   </button>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-255855c8=""
+    name="t-overlay"
+    persisted="true"
   >
     <div
       class="t-overlay"
@@ -47,7 +51,11 @@ exports[`Overlay > Overlay baseVue demo works fine 1`] = `
     </div>
   </transition-stub>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-255855c8=""
+    name="t-overlay"
+    persisted="true"
   >
     <div
       class="t-overlay t-overlay--transparent"
@@ -128,7 +136,11 @@ exports[`Overlay > Overlay mobileVue demo works fine 1`] = `
         </span>
       </button>
       <transition-stub
+        appear="false"
+        css="true"
         data-v-255855c8=""
+        name="t-overlay"
+        persisted="true"
       >
         <div
           class="t-overlay"
@@ -139,7 +151,11 @@ exports[`Overlay > Overlay mobileVue demo works fine 1`] = `
         </div>
       </transition-stub>
       <transition-stub
+        appear="false"
+        css="true"
         data-v-255855c8=""
+        name="t-overlay"
+        persisted="true"
       >
         <div
           class="t-overlay t-overlay--transparent"
diff --git a/src/picker/__test__/__snapshots__/demo.test.jsx.snap b/src/picker/__test__/__snapshots__/demo.test.jsx.snap
index 75144af3..e1ae6b3f 100644
--- a/src/picker/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/picker/__test__/__snapshots__/demo.test.jsx.snap
@@ -166,8 +166,18 @@ exports[`Picker > Picker baseVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -177,7 +187,12 @@ exports[`Picker > Picker baseVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -293,8 +308,18 @@ exports[`Picker > Picker baseVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -304,7 +329,12 @@ exports[`Picker > Picker baseVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -466,8 +496,18 @@ exports[`Picker > Picker baseVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -477,7 +517,12 @@ exports[`Picker > Picker baseVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -905,8 +950,18 @@ exports[`Picker > Picker cascadeVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -916,7 +971,12 @@ exports[`Picker > Picker cascadeVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -1230,8 +1290,18 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1241,7 +1311,12 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -1357,8 +1432,18 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1368,7 +1453,12 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -1530,8 +1620,18 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1541,7 +1641,12 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
@@ -1978,8 +2083,18 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
     <div
       class="t-popup"
     >
-      <transition-stub>
-        <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="fade"
+        persisted="false"
+      >
+        <transition-stub
+          appear="false"
+          css="true"
+          name="t-overlay"
+          persisted="true"
+        >
           <div
             class="t-overlay"
             style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -1989,7 +2104,12 @@ exports[`Picker > Picker mobileVue demo works fine 1`] = `
           </div>
         </transition-stub>
       </transition-stub>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="slide-bottom"
+        persisted="true"
+      >
         <div
           class="t-popup--content t-popup--content-bottom"
           style="display: none;"
diff --git a/src/popup/__test__/__snapshots__/demo.test.jsx.snap b/src/popup/__test__/__snapshots__/demo.test.jsx.snap
index 4befc573..cb423126 100644
--- a/src/popup/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/popup/__test__/__snapshots__/demo.test.jsx.snap
@@ -26,8 +26,18 @@ exports[`Popup > Popup customAnimationVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -37,7 +47,12 @@ exports[`Popup > Popup customAnimationVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-fade"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -107,8 +122,18 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -118,7 +143,12 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-top"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-top"
             style="display: none;"
@@ -158,8 +188,18 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -169,7 +209,12 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-bottom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-bottom"
             style="display: none;"
@@ -209,8 +254,18 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -220,7 +275,12 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-left"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-left"
             style="display: none;"
@@ -260,8 +320,18 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -271,7 +341,12 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-right"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-right"
             style="display: none;"
@@ -311,8 +386,18 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -322,7 +407,12 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade-zoom"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-center"
             style="display: none;"
@@ -362,8 +452,18 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
       <div
         class="t-popup"
       >
-        <transition-stub>
-          <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="fade"
+          persisted="false"
+        >
+          <transition-stub
+            appear="false"
+            css="true"
+            name="t-overlay"
+            persisted="true"
+          >
             <div
               class="t-overlay"
               style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -373,7 +473,12 @@ exports[`Popup > Popup mobileVue demo works fine 1`] = `
             </div>
           </transition-stub>
         </transition-stub>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="slide-fade"
+          persisted="true"
+        >
           <div
             class="t-popup--content t-popup--content-bottom"
             style="display: none;"
@@ -419,8 +524,18 @@ exports[`Popup > Popup placementBottomVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -430,7 +545,12 @@ exports[`Popup > Popup placementBottomVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-bottom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-bottom"
         style="display: none;"
@@ -473,8 +593,18 @@ exports[`Popup > Popup placementCenterVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -484,7 +614,12 @@ exports[`Popup > Popup placementCenterVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade-zoom"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-center"
         style="display: none;"
@@ -527,8 +662,18 @@ exports[`Popup > Popup placementLeftVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -538,7 +683,12 @@ exports[`Popup > Popup placementLeftVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-left"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-left"
         style="display: none;"
@@ -581,8 +731,18 @@ exports[`Popup > Popup placementRightVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -592,7 +752,12 @@ exports[`Popup > Popup placementRightVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-right"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-right"
         style="display: none;"
@@ -635,8 +800,18 @@ exports[`Popup > Popup placementTopVue demo works fine 1`] = `
   <div
     class="t-popup"
   >
-    <transition-stub>
-      <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="fade"
+      persisted="false"
+    >
+      <transition-stub
+        appear="false"
+        css="true"
+        name="t-overlay"
+        persisted="true"
+      >
         <div
           class="t-overlay"
           style="z-index: 1000; transition-duration: 300ms; display: none;"
@@ -646,7 +821,12 @@ exports[`Popup > Popup placementTopVue demo works fine 1`] = `
         </div>
       </transition-stub>
     </transition-stub>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="slide-top"
+      persisted="true"
+    >
       <div
         class="t-popup--content t-popup--content-top"
         style="display: none;"
diff --git a/src/radio/__test__/index.test.jsx b/src/radio/__test__/index.test.jsx
new file mode 100644
index 00000000..c238d4b1
--- /dev/null
+++ b/src/radio/__test__/index.test.jsx
@@ -0,0 +1,62 @@
+import { ref } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Radio from '../radio.vue';
+import RadioGroup from '../../radio-group/radio-group.vue';
+
+describe('Radio.vue', () => {
+  it('radio create', async () => {
+    const wrapper = mount(() => <Radio></Radio>);
+    expect(wrapper.classes()).toContain('t-radio');
+  });
+
+  it('disabled render', async () => {
+    const wrapper = mount(() => <Radio disabled></Radio>);
+    expect(wrapper.classes()).toContain('t-is-disabled');
+  });
+
+  it('checked render', async () => {
+    const checked = ref(true);
+    const wrapper = mount(() => <Radio modelValue={checked.value}></Radio>);
+    expect(wrapper.classes()).toContain('t-is-checked');
+  });
+
+  it('align render', async () => {
+    const wrapper1 = mount(() => <Radio></Radio>);
+    const wrapper2 = mount(() => <Radio align="right"></Radio>);
+    const wrap1 = wrapper1.find('.t-radio__icon-wrap');
+    const wrap2 = wrapper2.find('.t-radio__icon-wrap');
+    expect(wrap1.classes()).toContain('t-radio__icon-left-wrap');
+    expect(wrap2.classes()).toContain('t-radio__icon-right-wrap');
+  });
+
+  it('group create', async () => {
+    const wrapper = mount(() => (
+      <RadioGroup>
+        <Radio></Radio>
+        <Radio></Radio>
+        <Radio></Radio>
+      </RadioGroup>
+    ));
+    const radioList = wrapper.findAll('.t-radio');
+    expect(wrapper.classes()).toContain('t-radio-group');
+    expect(radioList.length).toBe(3);
+  });
+
+  it('change event', async () => {
+    const radio = ref('1');
+    const wrapper = mount(() => (
+      <RadioGroup v-model:value={radio.value}>
+        <Radio name="radio" value="1" label="单选"></Radio>
+        <Radio name="radio" value="2" label="单选"></Radio>
+        <Radio name="radio" value="3" label="单选"></Radio>
+      </RadioGroup>
+    ));
+    const [radio1, radio2] = wrapper.findAll('.t-radio');
+    const [, original2] = wrapper.findAll('.t-radio__original-left');
+    expect(radio1.classes()).toContain('t-is-checked');
+    await original2.trigger('click');
+    expect(radio2.classes()).toContain('t-is-checked');
+    expect(radio.value).toBe('2');
+  });
+});
diff --git a/src/rate/__test__/index.test.jsx b/src/rate/__test__/index.test.jsx
new file mode 100644
index 00000000..5c9eec50
--- /dev/null
+++ b/src/rate/__test__/index.test.jsx
@@ -0,0 +1,82 @@
+import { nextTick, ref } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Rate from '../rate.vue';
+import { StarFilledIcon, StarIcon } from 'tdesign-icons-vue-next';
+
+describe('Rate.vue', () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Rate />);
+    expect(wrapper.classes()).toContain('t-rate');
+    const items = wrapper.findAll('.t-rate--item');
+    expect(items.length).toBe(5);
+    const icon = wrapper.findComponent(StarFilledIcon);
+    expect(icon.exists()).toBeTruthy();
+  });
+
+  it('count render', async () => {
+    const wrapper = mount(() => <Rate count={10} />);
+    const items = wrapper.findAll('.t-rate--item');
+    expect(items.length).toBe(10);
+  });
+
+  it('gap', async () => {
+    const wrapper = mount(() => <Rate gap={10} />);
+    const items = wrapper.findAll('.t-rate--item');
+    for (let i = 0; i < items.length - 1; i++) {
+      expect(getComputedStyle(items[i].element, null).marginRight).toBe('10px');
+    }
+    expect(getComputedStyle(items[items.length - 1].element, null).marginRight).toBe('0px');
+  });
+
+  it('variant render', async () => {
+    const wrapper = mount(() => <Rate variant="outline" />);
+    const icon = wrapper.findComponent(StarIcon);
+    expect(icon.exists()).toBeTruthy();
+  });
+
+  it('change event', async () => {
+    const value = ref(1);
+    const wrapper = mount(() => <Rate v-model={value.value} />);
+    const icons = wrapper.findAll('.t-rate--icon');
+    await icons[1].trigger('click');
+    await nextTick();
+    expect(value.value).toBe(2);
+  });
+
+  it('allowHalf event', async () => {
+    const value = ref(0);
+    const wrapper = mount(() => <Rate v-model={value.value} allowHalf />);
+    const leftIcons = wrapper.findAll('.t-rate--icon-left');
+    const rightIcons = wrapper.findAll('.t-rate--icon-right');
+    await leftIcons[0].trigger('click');
+    await nextTick();
+    expect(leftIcons[0].exists()).toBeTruthy();
+    expect(value.value).toBe(0.5);
+    await rightIcons[0].trigger('click');
+    await nextTick();
+    expect(rightIcons[0].exists()).toBeTruthy();
+    expect(value.value).toBe(1);
+  });
+
+  it('clearable', async () => {
+    const value = ref(1);
+    const wrapper = mount(() => <Rate v-model={value.value} clearable />);
+    const icons = wrapper.findAll('.t-rate--icon');
+    await icons[1].trigger('click');
+    await nextTick();
+    expect(value.value).toBe(2);
+    await icons[1].trigger('click');
+    await nextTick();
+    expect(value.value).toBe(0);
+  });
+
+  it('disabled render', async () => {
+    const value = ref(1);
+    const wrapper = mount(() => <Rate v-model={value.value} disabled />);
+    const icons = wrapper.findAll('.t-rate--icon');
+    await icons[1].trigger('click');
+    await nextTick();
+    expect(value.value).toBe(1);
+  });
+});
diff --git a/src/result/__test__/index.test.jsx b/src/result/__test__/index.test.jsx
new file mode 100644
index 00000000..02bba28d
--- /dev/null
+++ b/src/result/__test__/index.test.jsx
@@ -0,0 +1,60 @@
+import { h } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import { InfoCircleIcon, CheckCircleIcon, CloseCircleIcon, AppIcon } from 'tdesign-icons-vue-next';
+import Result from '../result.vue';
+import Image from '../../image/image.vue'
+
+const appIcon = () => h(AppIcon);
+const imageUrl = 'https://tdesign.gtimg.com/mobile/demos/avatar_1.png';
+
+describe('Result.vue', () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Result></Result>);
+    expect(wrapper.classes()).toContain('t-result');
+  });
+
+  it('title render', async () => {
+    const wrapper = mount(() => <Result title="标题"></Result>);
+    const title = wrapper.find('.t-result__title');
+    expect(title.exists()).toBeTruthy();
+    expect(title.text()).toBe('标题');
+  });
+
+  it('description render', async () => {
+    const wrapper = mount(() => <Result description="描述"></Result>);
+    const description = wrapper.find('.t-result__description');
+    expect(description.exists()).toBeTruthy();
+    expect(description.text()).toBe('描述');
+  });
+
+  it('icon render', async () => {
+    const wrapper = mount(() => <Result icon={appIcon}></Result>);
+    const icon = wrapper.find('.t-result__icon');
+    expect(icon.exists()).toBeTruthy();
+    expect(wrapper.findComponent(AppIcon).exists()).toBeTruthy();
+  });
+
+  it('image render', async () => {
+    const slots = {
+      image: () => <Image src={imageUrl} />
+    }
+    const wrapper = mount(() => <Result v-slots={slots}></Result>);
+    const img = wrapper.find('.t-image > img');
+    expect(img.exists()).toBeTruthy();
+    expect(img.attributes('src')).toBe(imageUrl);
+  });
+
+  it('theme render', async () => {
+    const wrapper1 = mount(() => <Result theme="success"></Result>);
+    const wrapper2 = mount(() => <Result theme="warning"></Result>);
+    const wrapper3 = mount(() => <Result theme="error"></Result>);
+
+    expect(wrapper1.classes()).toContain('t-result--theme-success');
+    expect(wrapper1.findComponent(CheckCircleIcon).exists()).toBeTruthy();
+    expect(wrapper2.classes()).toContain('t-result--theme-warning');
+    expect(wrapper2.findComponent(InfoCircleIcon).exists()).toBeTruthy();
+    expect(wrapper3.classes()).toContain('t-result--theme-error');
+    expect(wrapper3.findComponent(CloseCircleIcon).exists()).toBeTruthy();
+  });
+});
diff --git a/src/skeleton/__test__/index.test.jsx b/src/skeleton/__test__/index.test.jsx
new file mode 100644
index 00000000..0ab41916
--- /dev/null
+++ b/src/skeleton/__test__/index.test.jsx
@@ -0,0 +1,33 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import Skeleton from '../skeleton.vue';
+
+describe('Skeleton.vue', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Skeleton />);
+    expect(wrapper.classes()).toContain('t-skeleton');
+    const content = wrapper.find('.t-skeleton__content');
+    expect(content.exists()).toBeTruthy();
+  });
+
+  it('theme render', async () => {
+    const wrapper1 = mount(() => <Skeleton theme="avatar" />);
+    const wrapper2 = mount(() => <Skeleton theme="image" />);
+    const wrapper3 = mount(() => <Skeleton theme="paragraph" />);
+    const content1 = wrapper1.find('.t-skeleton--type-circle');
+    const content2 = wrapper2.find('.t-skeleton--type-rect');
+    const content3 = wrapper3.find('.t-skeleton--type-text');
+    expect(content1.exists()).toBeTruthy();
+    expect(content2.exists()).toBeTruthy();
+    expect(content3.exists()).toBeTruthy();
+  });
+
+  it('animation render', async () => {
+    const wrapper1 = mount(() => <Skeleton animation="gradient" />);
+    const wrapper2 = mount(() => <Skeleton animation="flashed" />);
+    const row1 = wrapper1.find('.t-skeleton__row > .t-skeleton__col');
+    const row2 = wrapper2.find('.t-skeleton__row > .t-skeleton__col');
+    expect(row1.classes()).toContain('t-skeleton--animation-gradient');
+    expect(row2.classes()).toContain('t-skeleton--animation-flashed');
+  });
+});
diff --git a/src/steps/__test__/__snapshots__/index.test.jsx.snap b/src/steps/__test__/__snapshots__/index.test.jsx.snap
new file mode 100644
index 00000000..6b3c57c1
--- /dev/null
+++ b/src/steps/__test__/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,94 @@
+// Vitest Snapshot v1
+
+exports[`steps > props > : layout 1`] = `
+<div
+  class="t-steps t-steps--vertical t-steps--readonly t-steps--default-anchor"
+>
+  
+  <div
+    class="t-step t-step--error"
+  >
+    <div
+      class="t-step__inner"
+    >
+      <div
+        class="t-step-icon"
+      >
+        <div
+          class="t-step-icon__number"
+        >
+          <svg
+            class="t-icon t-icon-close"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
+          >
+            <path
+              d="M8 8.92L11.08 12l.92-.92L8.92 8 12 4.92 11.08 4 8 7.08 4.92 4 4 4.92 7.08 8 4 11.08l.92.92L8 8.92z"
+              fill="currentColor"
+              fill-opacity="0.9"
+            />
+          </svg>
+        </div>
+      </div>
+      <div
+        class="t-step-content"
+      >
+        <div
+          class="t-step-title"
+        >
+          步骤描述1
+        </div>
+        <div
+          class="t-step-description"
+        >
+          辅助信息文字最多两行
+        </div>
+        <div
+          class="t-step-extra"
+        >
+          <!---->
+        </div>
+      </div>
+    </div>
+  </div>
+  <div
+    class="t-step t-step--process"
+  >
+    <div
+      class="t-step__inner"
+    >
+      <div
+        class="t-step-icon"
+      >
+        <div
+          class="t-step-icon__number"
+        >
+          2
+        </div>
+      </div>
+      <div
+        class="t-step-content"
+      >
+        <div
+          class="t-step-title"
+        >
+          步骤描述1
+        </div>
+        <div
+          class="t-step-description"
+        >
+          辅助信息文字最多两行
+        </div>
+        <div
+          class="t-step-extra"
+        >
+          <!---->
+        </div>
+      </div>
+    </div>
+  </div>
+  
+</div>
+`;
diff --git a/src/steps/__test__/index.test.jsx b/src/steps/__test__/index.test.jsx
new file mode 100644
index 00000000..9d3c119d
--- /dev/null
+++ b/src/steps/__test__/index.test.jsx
@@ -0,0 +1,240 @@
+import { mount } from '@vue/test-utils';
+import { includes } from 'lodash';
+import { describe, it, expect, vi } from 'vitest';
+import { ref } from 'vue';
+import {Steps, Step}  from '../../steps';
+const items = [
+  {
+    title: "步骤描述1",
+    content: "辅助信息文字最多两行",
+  },
+  {
+    title: "步骤描述1",
+    content: "辅助信息文字最多两行",
+  }
+]
+const itemStatus = [
+  {
+    title: "步骤描述1",
+    content: "辅助信息文字最多两行",
+    status: 'error'
+  },
+  {
+    title: "步骤描述1",
+    content: "辅助信息文字最多两行",
+    status: 'process'
+  }
+]
+describe('steps', () => {
+  describe('props', () => {
+    it(': theme', async () => {
+      const current = ref(0);
+      const theme = 'dot';
+      const layout = 'vertical';
+      const onChange = vi.fn();
+      const event = {};
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Steps v-model={current.value} theme={theme} layout={layout} onChange={onChange} >
+              {{
+                default: items.map((item, index) => {
+                  return <Step title={item.title} content={item.content} />
+                })
+              }}
+            </Steps>
+          )
+        }
+      })
+      const $stepItems = wrapper.findAllComponents(Step);
+      expect($stepItems.length).toEqual(items.length);
+      $stepItems.map(async (item, index) => {
+        expect(item.find(`.t-step-icon__number`).text()).toEqual('');
+        expect(item.find(`.t-step-icon__number`).classes().includes(`t-step-icon__dot`)).toBeTruthy();
+        expect(item.find(`.t-step-title`).text()).toEqual(items[index].title);
+        expect(item.find(`.t-step-description`).text()).toEqual(items[index].content);
+        await item.find(`.t-step__inner`).trigger('click', event)
+      })
+
+      // theme = 'dot' 仅在 layout = 'vertical' 下有效, 不会触发 change
+      expect(onChange).toHaveBeenCalledTimes(0);
+    });
+
+    it(': readonly', async () => {
+      const current = ref(0);
+      const onChange = vi.fn();
+      const event = {};
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Steps v-model={current.value} readonly={true} onChange={onChange} >
+              {{
+                default: items.map((item, index) => {
+                  return <Step title={item.title} content={item.content} />
+                })
+              }}
+            </Steps>
+          )
+        }
+      })
+      const $stepItems = wrapper.findAllComponents(Step);
+      expect($stepItems.length).toEqual(items.length);
+      $stepItems.map(async (item, index) => {
+        expect(item.find(`.t-step-icon__number`).text()).toEqual(String(index + 1));
+        expect(item.find(`.t-step-title`).text()).toEqual(items[index].title);
+        expect(item.find(`.t-step-description`).text()).toEqual(items[index].content);
+        await item.find(`.t-step__inner`).trigger('click', event)
+      })
+
+      // readonly:true, 只读模式, 不会触发 change
+      expect(onChange).toHaveBeenCalledTimes(0);
+    });
+
+    it(': options', async () => {
+      const current = ref(1);
+      const options = ref([
+        {
+          title: '步骤描述',
+          content: '辅助信息文字最多两行',
+        },
+        {
+          title: '选中步骤',
+          content: '辅助信息文字最多两行',
+        },
+        {
+          title: '未完成',
+          content: '辅助信息文字最多两行',
+        },
+      ]);
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Steps v-model={current.value} options={options}/>
+          )
+        }
+      })
+      const $stepItems = wrapper.findAllComponents(Step);
+      expect($stepItems.length).toEqual(options.value.length);
+      $stepItems.map(async (item, index) => {
+        expect(item.find(`.t-step-icon__number`).text()).toEqual(String(index + 1));
+        expect(item.find(`.t-step-title`).text()).toEqual(options.value[index].title);
+        expect(item.find(`.t-step-description`).text()).toEqual(options.value[index].content);
+
+        // 默认选中 下标为1的子项, 则 1 状态为 process,  1 之前的子项状态为 finish,  1 之后的子项状态为 default
+        const classes = item.find(`.t-step`).classes();
+        if (index < 1) {
+          expect(classes.includes(`t-step--finish`)).toBeTruthy();
+        } else if(index === 1) {
+          expect(classes.includes(`t-step--process`)).toBeTruthy();
+        }else {
+          expect(classes.includes(`t-step--default`)).toBeTruthy();
+        }
+      })
+    });
+    it(': layout', () => {
+      const current = ref(1);
+      const layout = 'vertical';
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Steps v-model={current.value} readonly={true} layout={layout} >
+              {{
+                default: itemStatus.map((item, index) => {
+                  return <Step title={item.title} content={item.content} status={item.status} />
+                })
+              }}
+            </Steps>
+          )
+        }
+      })
+      expect(wrapper.element).toMatchSnapshot();
+      expect(wrapper.find(`.t-steps`).classes().includes(`t-steps--vertical`)).toBeTruthy();
+      const $stepItems = wrapper.findAllComponents(Step);
+      expect($stepItems.length).toEqual(itemStatus.length);
+
+      $stepItems.map((item, index) => {
+        const classes = item.find(`.t-step`).classes();
+        expect(classes.includes(`t-step--${itemStatus[index].status}`)).toBeTruthy();
+      })
+    })
+  });
+
+  describe('StepItem Slots', () => {
+    it(': content && title', () => {
+      const current = ref(1);
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Steps v-model={current.value}>
+              {{
+                default: items.map((item, index) => {
+                  return (
+                    <Step>
+                      {{
+                        title: item.title,
+                        content: item.content,
+                      }}
+                    </Step>
+                  )
+                })
+              }}
+            </Steps>
+          )
+        }
+      })
+      const $stepItems = wrapper.findAllComponents(Step);
+      expect($stepItems.length).toEqual(items.length);
+      $stepItems.map((item, index) => {
+        expect(item.find(`.t-step-icon__number`).text()).toEqual(String(index + 1));
+        expect(item.find(`.t-step-title`).text()).toEqual(items[index].title);
+        expect(item.find(`.t-step-description`).text()).toEqual(items[index].content);
+      })
+    });
+  });
+
+  describe('event', () => {
+    it(': change', async () => {
+      const current = ref(1);
+      const options = ref([
+        {
+          title: '步骤描述',
+          content: '辅助信息文字最多两行',
+        },
+        {
+          title: '选中步骤',
+          content: '辅助信息文字最多两行',
+        },
+        {
+          title: '未完成',
+          content: '辅助信息文字最多两行',
+        },
+      ]);
+      const onChange = vi.fn();
+      const event = {};
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Steps v-model={current.value} options={options} onChange={onChange} />
+          )
+        }
+      })
+      const $stepItems = wrapper.findAllComponents(Step);
+      expect($stepItems.length).toEqual(options.value.length);
+      // 点击第 2 项, 触发 change, 此时 current = 2,
+      await $stepItems[2].find(`.t-step__inner`).trigger('click', event)
+      expect(onChange).toHaveBeenCalledTimes(1);
+
+      $stepItems.map(async (item, index) => {
+        //  current = 2, 则 2 状态为 process,  2 之前的子项状态为 finish,  2 之后的子项状态为 default
+        const classes = item.find(`.t-step`).attributes('class');
+        if (index < 2) {
+          expect(classes.includes(`t-step--finish`)).toBeTruthy();
+        } else if(index === 2) {
+          expect(classes.includes(`t-step--process`)).toBeTruthy();
+        }else {
+          expect(classes.includes(`t-step--default`)).toBeTruthy();
+        }
+      })
+    });
+  });
+})
diff --git a/src/tab-bar/__test__/__snapshots__/demo.test.jsx.snap b/src/tab-bar/__test__/__snapshots__/demo.test.jsx.snap
index 4c8e7cff..a376dddf 100644
--- a/src/tab-bar/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/tab-bar/__test__/__snapshots__/demo.test.jsx.snap
@@ -34,7 +34,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -59,7 +64,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -94,7 +104,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -119,7 +134,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -144,7 +164,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -179,7 +204,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -204,7 +234,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -229,7 +264,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -254,7 +294,12 @@ exports[`TabBar > TabBar baseVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -300,7 +345,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -345,7 +395,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -389,7 +444,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -425,7 +485,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -470,7 +535,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -514,7 +584,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -558,7 +633,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -594,7 +674,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -639,7 +724,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -683,7 +773,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -727,7 +822,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -771,7 +871,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -807,7 +912,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -852,7 +962,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -896,7 +1011,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -940,7 +1060,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -984,7 +1109,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -1028,7 +1158,12 @@ exports[`TabBar > TabBar iconTextVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -1100,7 +1235,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1125,7 +1265,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1160,7 +1305,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1185,7 +1335,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1210,7 +1365,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1245,7 +1405,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1270,7 +1435,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1295,7 +1465,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1320,7 +1495,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1389,7 +1569,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1433,7 +1618,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1487,7 +1677,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1531,7 +1726,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1575,7 +1775,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1629,7 +1834,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1673,7 +1883,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1717,7 +1932,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1761,7 +1981,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1815,7 +2040,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1859,7 +2089,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1903,7 +2138,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1947,7 +2187,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -1991,7 +2236,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2057,7 +2307,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2113,7 +2368,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2169,7 +2429,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2225,7 +2490,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2287,7 +2557,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2324,7 +2599,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2371,7 +2651,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2408,7 +2693,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2445,7 +2735,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2492,7 +2787,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2529,7 +2829,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2566,7 +2871,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2603,7 +2913,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2650,7 +2965,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2687,7 +3007,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2724,7 +3049,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2761,7 +3091,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2798,7 +3133,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
           </div>
           <!--v-if-->
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2848,7 +3188,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2874,7 +3219,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2902,7 +3252,12 @@ exports[`TabBar > TabBar mobileVue demo works fine 1`] = `
             
           </div>
         </div>
-        <transition-stub>
+        <transition-stub
+          appear="false"
+          css="true"
+          name="spread"
+          persisted="false"
+        >
           <!--v-if-->
         </transition-stub>
       </div>
@@ -2960,7 +3315,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -2997,7 +3357,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3044,7 +3409,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3081,7 +3451,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3118,7 +3493,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3165,7 +3545,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3202,7 +3587,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3239,7 +3629,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3276,7 +3671,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3323,7 +3723,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3360,7 +3765,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3397,7 +3807,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3434,7 +3849,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3471,7 +3891,12 @@ exports[`TabBar > TabBar pureIconVue demo works fine 1`] = `
         </div>
         <!--v-if-->
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3515,7 +3940,12 @@ exports[`TabBar > TabBar textSpreadVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3540,7 +3970,12 @@ exports[`TabBar > TabBar textSpreadVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
@@ -3568,7 +4003,12 @@ exports[`TabBar > TabBar textSpreadVue demo works fine 1`] = `
           
         </div>
       </div>
-      <transition-stub>
+      <transition-stub
+        appear="false"
+        css="true"
+        name="spread"
+        persisted="false"
+      >
         <!--v-if-->
       </transition-stub>
     </div>
diff --git a/src/tab-bar/__test__/__snapshots__/index.test.jsx.snap b/src/tab-bar/__test__/__snapshots__/index.test.jsx.snap
index 63a6bfcd..52253cd0 100644
--- a/src/tab-bar/__test__/__snapshots__/index.test.jsx.snap
+++ b/src/tab-bar/__test__/__snapshots__/index.test.jsx.snap
@@ -49,7 +49,12 @@ exports[`TabBar > props > snapshot 1`] = `
       </div>
       <!--v-if-->
     </div>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="spread"
+      persisted="false"
+    >
       <!--v-if-->
     </transition-stub>
   </div>
@@ -95,7 +100,12 @@ exports[`TabBar > props > snapshot 1`] = `
       </div>
       <!--v-if-->
     </div>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="spread"
+      persisted="false"
+    >
       <!--v-if-->
     </transition-stub>
   </div>
@@ -142,7 +152,12 @@ exports[`TabBar > props > snapshot 1`] = `
       </div>
       <!--v-if-->
     </div>
-    <transition-stub>
+    <transition-stub
+      appear="false"
+      css="true"
+      name="spread"
+      persisted="false"
+    >
       <!--v-if-->
     </transition-stub>
   </div>
diff --git a/src/tag/__test__/index.test.jsx b/src/tag/__test__/index.test.jsx
new file mode 100644
index 00000000..1b7a96a4
--- /dev/null
+++ b/src/tag/__test__/index.test.jsx
@@ -0,0 +1,146 @@
+import { h } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect } from 'vitest';
+import { AppIcon as TIconApp } from 'tdesign-icons-vue-next';
+import Tag from '../tag.vue';
+import TCheckTag from '../check-tag.vue';
+
+const TEXT = 'tdesign-mobile-vue';
+const iconFunc = () => h(TIconApp);
+
+const themeList = ['danger', 'default', 'primary', 'success', 'warning'];
+const variantList = ['dark', 'light', 'light-outline', 'outline'];
+const sizeList = ['large', 'medium', 'small'];
+const shapeList = ['mark', 'round', 'square'];
+
+describe('Tag.vue', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => <Tag>{TEXT}</Tag>);
+    expect(wrapper.text()).toBe(TEXT);
+    expect(wrapper.classes()).toContain('t-tag');
+  });
+
+  it('theme render', async () => {
+    themeList.forEach((t) => {
+      const wrapper = mount(() => <Tag theme={t}>{TEXT}</Tag>);
+      expect(wrapper.classes()).toContain(`t-tag--theme-${t}`);
+    });
+  });
+
+  it('variant render', async () => {
+    variantList.forEach((v) => {
+      const wrapper = mount(() => <Tag variant={v}>{TEXT}</Tag>);
+      expect(wrapper.classes()).toContain(`t-tag--variant-${v}`);
+    });
+  });
+
+  it('size render', async () => {
+    sizeList.forEach((s) => {
+      const wrapper = mount(() => <Tag size={s}>{TEXT}</Tag>);
+      expect(wrapper.classes()).toContain(`t-tag--size-${s}`);
+    });
+  });
+
+  it('shape render', async () => {
+    shapeList.forEach((s) => {
+      const wrapper = mount(() => <Tag shape={s}>{TEXT}</Tag>);
+      expect(wrapper.classes()).toContain(`t-tag--shape-${s}`);
+    });
+  });
+
+  it('icon render', async () => {
+    const wrapper = mount(() => <Tag icon={iconFunc}>{TEXT}</Tag>);
+    expect(wrapper.findComponent(TIconApp)).toBeTruthy();
+  });
+
+  it('disabled render', async () => {
+    const wrapper = mount(() => <Tag disabled>{TEXT}</Tag>);
+    expect(wrapper.classes()).toContain('t-is-disabled');
+  });
+
+  it('maxWidth render', async () => {
+    const wrapper = mount(() => <Tag maxWidth="130px">{TEXT}</Tag>);
+    expect(getComputedStyle(wrapper.element, null).maxWidth).toBe('130px');
+  });
+
+  it('click render', async () => {
+    const wrapper = mount(() => <Tag>{TEXT}</Tag>);
+    await wrapper.trigger('click');
+    expect(wrapper.emitted('click')).toBeTruthy();
+  });
+
+  it('close render', async () => {
+    const wrapper = mount(() => <Tag closable>{TEXT}</Tag>);
+    const com = wrapper.findComponent(Tag);
+    expect(wrapper.classes()).toContain('t-tag--closable');
+    const closeBtn = com.find('.t-tag__icon-close');
+    expect(closeBtn.exists()).toBeTruthy();
+
+    await closeBtn.trigger('click');
+    expect(com.emitted('close')).toBeTruthy();
+  });
+});
+
+describe('CheckTag render', async () => {
+  it('create', async () => {
+    const wrapper = mount(() => <TCheckTag>{TEXT}</TCheckTag>);
+    expect(wrapper.classes()).toContain('t-tag');
+    expect(wrapper.classes()).toContain('t-tag--checkable');
+  });
+
+  it('checked render', async () => {
+    const wrapper = mount(() => <TCheckTag modelValue={true}>{TEXT}</TCheckTag>);
+    expect(wrapper.classes()).toContain('t-tag--checked');
+  });
+
+  it('disabled render', async () => {
+    const wrapper = mount(() => <TCheckTag disabled>{TEXT}</TCheckTag>);
+    expect(wrapper.classes()).toContain('t-is-disabled');
+  });
+
+  it('icon render', async () => {
+    const wrapper = mount(() => <TCheckTag icon={iconFunc}>{TEXT}</TCheckTag>);
+    expect(wrapper.findComponent(TIconApp)).toBeTruthy();
+  });
+
+  it('size render', async () => {
+    sizeList.forEach((s) => {
+      const wrapper = mount(() => <TCheckTag size={s}>{TEXT}</TCheckTag>);
+      expect(wrapper.classes()).toContain(`t-tag--size-${s}`);
+    });
+  });
+
+  it('shape render', async () => {
+    shapeList.forEach((s) => {
+      const wrapper = mount(() => <TCheckTag shape={s}>{TEXT}</TCheckTag>);
+      expect(wrapper.classes()).toContain(`t-tag--shape-${s}`);
+    });
+  });
+
+  it('click render', async () => {
+    const wrapper = mount(() => <TCheckTag>{TEXT}</TCheckTag>);
+    await wrapper.trigger('click');
+    expect(wrapper.emitted('click')).toBeTruthy();
+  });
+
+  it('change render', async () => {
+    const wrapper = mount({
+      data() {
+        return {
+          checked: false,
+        };
+      },
+      render() {
+        return (
+          <TCheckTag onChange={() => (this.checked = !this.checked)} checked={this.checked}>
+            {TEXT}
+          </TCheckTag>
+        );
+      },
+    });
+    await wrapper.trigger('click');
+    expect(wrapper.vm.checked).toBe(true);
+    await wrapper.trigger('click');
+    expect(wrapper.vm.checked).toBe(false);
+  });
+});
diff --git a/src/textarea/__test__/index.test.jsx b/src/textarea/__test__/index.test.jsx
new file mode 100644
index 00000000..1d2425c1
--- /dev/null
+++ b/src/textarea/__test__/index.test.jsx
@@ -0,0 +1,78 @@
+import { nextTick, ref } from 'vue';
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi } from 'vitest';
+import Textarea from '../textarea.vue';
+
+describe('Textarea.vue', () => {
+  it('create', async () => {
+    const value = ref('123');
+    const wrapper = mount(() => <Textarea v-model={value.value} />);
+    expect(wrapper.classes()).toContain('t-textarea');
+    const textarea = wrapper.find('textarea');
+    expect(textarea.exists()).toBeTruthy();
+    expect(textarea.element.value).toBe('123');
+  });
+
+  it('placeholder render', async () => {
+    const wrapper = mount(<Textarea placeholder="请输入" />);
+    const input = wrapper.find('textarea');
+    expect(input.attributes('placeholder')).toBe('请输入');
+  });
+
+  it('label render', async () => {
+    const wrapper = mount(<Textarea label="标题" />);
+    const label = wrapper.find('.t-textarea__name');
+    expect(label.exists()).toBeTruthy();
+    expect(label.text()).toBe('标题');
+  });
+
+  it('disabled render', async () => {
+    const value = ref('123');
+    const wrapper = mount(<Textarea label="标题" v-model={value.value} disabled />);
+    const label = wrapper.find('.t-textarea__wrapper');
+    expect(label.classes()).toContain('t-textarea-is-disabled');
+    const textarea = wrapper.find('textarea');
+    expect(textarea.element.hasAttribute('disabled')).toBeTruthy();
+  });
+
+  it('maxlength render', async () => {
+    const value = ref('');
+    const wrapper = mount(<Textarea label="标题" v-model={value.value} maxlength={10} />);
+    const textarea = wrapper.find('textarea');
+    expect(textarea.element.getAttribute('maxlength')).toBe('10');
+  });
+
+  it('blur trigger', async () => {
+    const onBlur = vi.fn();
+    const wrapper = mount(<Textarea label="标题" onBlur={onBlur} />);
+    const textarea = wrapper.find('textarea');
+    await textarea.trigger('blur');
+    expect(onBlur).toBeCalled();
+  });
+
+  it('focus trigger', async () => {
+    const onFocus = vi.fn();
+    const wrapper = mount(<Textarea label="标题" onFocus={onFocus} />);
+    const textarea = wrapper.find('textarea');
+    await textarea.trigger('focus');
+    expect(onFocus).toBeCalled();
+  });
+
+  it('change trigger', async () => {
+    const data = ref('');
+    const value = ref('');
+    const handleChange = (val) => {
+      value.value = val;
+    };
+    const wrapper = mount(<Textarea label="标题" v-model={data.value} onChange={handleChange} />);
+    const el = wrapper.find('textarea').element;
+    await nextTick();
+    const simulateEvent = (text, event) => {
+      el.value = text;
+      el.dispatchEvent(new Event(event));
+    };
+    simulateEvent('2', 'input');
+    await nextTick();
+    expect(value.value).toBe('2');
+  });
+});
diff --git a/src/upload/__test__/__snapshots__/demo.test.jsx.snap b/src/upload/__test__/__snapshots__/demo.test.jsx.snap
index b53c4a32..904a28cf 100644
--- a/src/upload/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/upload/__test__/__snapshots__/demo.test.jsx.snap
@@ -56,7 +56,11 @@ exports[`Upload > Upload baseMultipleVue demo works fine 1`] = `
           type="file"
         />
         <transition-stub
+          appear="false"
           class="t-image-viewer"
+          css="true"
+          name="t-overlay"
+          persisted="true"
         >
           <div
             class="t-overlay"
@@ -71,7 +75,11 @@ exports[`Upload > Upload baseMultipleVue demo works fine 1`] = `
     </div>
   </div>
   <transition-stub
+    appear="false"
+    css="true"
     data-v-0c024305=""
+    name="message"
+    persisted="false"
   >
     <!--v-if-->
   </transition-stub>
@@ -133,7 +141,11 @@ exports[`Upload > Upload baseSingleVue demo works fine 1`] = `
           type="file"
         />
         <transition-stub
+          appear="false"
           class="t-image-viewer"
+          css="true"
+          name="t-overlay"
+          persisted="true"
         >
           <div
             class="t-overlay"
@@ -228,7 +240,11 @@ exports[`Upload > Upload mobileVue demo works fine 1`] = `
           type="file"
         />
         <transition-stub
+          appear="false"
           class="t-image-viewer"
+          css="true"
+          name="t-overlay"
+          persisted="true"
         >
           <div
             class="t-overlay"
@@ -282,7 +298,11 @@ exports[`Upload > Upload mobileVue demo works fine 1`] = `
           type="file"
         />
         <transition-stub
+          appear="false"
           class="t-image-viewer"
+          css="true"
+          name="t-overlay"
+          persisted="true"
         >
           <div
             class="t-overlay"