Repository: Tencent/tdesign-mobile-vue
Pull request #304 (2022-08-24 02:24:41)

test: add unit test for stepper
<!--
首先，感谢你的贡献！😄
请阅读并遵循 [TDesign 贡献指南](https://github.com/Tencent/tdesign/blob/main/docs/contributing.md)，填写以下 pull request 的信息。
PR 在维护者审核通过后会合并，谢谢！
-->

### 🤔 这个 PR 的性质是？
- [x] 演示代码改进
- [x] 代码风格优化
- [x] 测试用例

### 📝 更新日志

<!--
从用户角度描述具体变化，以及可能的 breaking change 和其他风险。
-->

- feat(Stepper): 新增单元测试
- fix(Stepper): 修复输入非 `number` 字符时，出现 `NaN` 问题
- fix(Stepper): 修复输入值能超出 `max` 的问题
- fix(Stepper): 修复 `value` 值小于 `min` 或超出 `max` 时，不触发 `overlimit` 的问题



github-actions[bot]: [<img height="96" alt="失败" src="https://user-images.githubusercontent.com/5378891/75333447-1e63a280-58c1-11ea-975d-235367fd1522.png">](https://github.com/Tencent/tdesign-mobile-vue/actions/runs/2916080629)
<!-- AUTO_PREVIEW_HOOK -->
github-actions[bot]: **Size Change:** +77 B (0%) 

**Total Size:** 247 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +49 B (0%) |
| `./dist/tdesign.min.js` | 76.1 kB | +28 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 247 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +49 B (0%) |
| `./dist/tdesign.min.js` | 76.1 kB | +28 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 248 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +48 B (0%) |
| `./dist/tdesign.min.js` | 76.2 kB | +25 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 248 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +48 B (0%) |
| `./dist/tdesign.min.js` | 76.2 kB | +25 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 248 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +49 B (0%) |
| `./dist/tdesign.min.js` | 76.6 kB | +24 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 248 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +45 B (0%) |
| `./dist/tdesign.min.js` | 76.6 kB | +11 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 248 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +45 B (0%) |
| `./dist/tdesign.min.js` | 76.6 kB | +11 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


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

**Total Size:** 248 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 126 kB | +45 B (0%) |
| `./dist/tdesign.min.js` | 76.6 kB | +11 B (0%) |

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

| Filename | Size |
| :--- | :---: |
| `./dist/tdesign.css` | 23.5 kB |
| `./dist/tdesign.min.css` | 21.8 kB |

</details>


LeeJim: 代码冲突了

Patch:
diff --git a/src/stepper/demos/base.vue b/src/stepper/demos/base.vue
index c4a40184..5229f6f7 100644
--- a/src/stepper/demos/base.vue
+++ b/src/stepper/demos/base.vue
@@ -1,21 +1,13 @@
 <template>
-  <div class="cell-base">
-    <t-cell-group title="基础步进器">
-      <t-cell title="标题文字">
-        <t-stepper v-model.number="number" :step="1" :min="0" :max="100" />
-      </t-cell>
-    </t-cell-group>
+  <div class="stepper-demo">
+    <t-cell title="标题文字">
+      <t-stepper v-model="number" />
+    </t-cell>
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 
-export default defineComponent({
-  setup() {
-    return {
-      number: ref(10),
-    };
-  },
-});
+const number = ref(0);
 </script>
diff --git a/src/stepper/demos/disable-input.vue b/src/stepper/demos/disable-input.vue
index 2a0c735a..7ab5a8e5 100644
--- a/src/stepper/demos/disable-input.vue
+++ b/src/stepper/demos/disable-input.vue
@@ -1,21 +1,13 @@
 <template>
-  <div class="cell-base">
-    <t-cell-group title="禁止输入">
-      <t-cell title="禁止输入">
-        <t-stepper v-model.number="number" :step="1" :min="0" :max="100" disable-input />
-      </t-cell>
-    </t-cell-group>
+  <div class="stepper-demo">
+    <t-cell title="禁止输入">
+      <t-stepper v-model="number" :step="1" :min="0" :max="100" disable-input />
+    </t-cell>
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 
-export default defineComponent({
-  setup() {
-    return {
-      number: ref(10),
-    };
-  },
-});
+const number = ref(0);
 </script>
diff --git a/src/stepper/demos/disabled.vue b/src/stepper/demos/disabled.vue
index bc6a9a76..5488c73d 100644
--- a/src/stepper/demos/disabled.vue
+++ b/src/stepper/demos/disabled.vue
@@ -1,21 +1,13 @@
 <template>
-  <div class="cell-base">
-    <t-cell-group title="禁用状态">
-      <t-cell title="失效">
-        <t-stepper v-model.number="number" :step="1" :min="0" :max="100" disabled />
-      </t-cell>
-    </t-cell-group>
+  <div class="stepper-demo">
+    <t-cell title="禁用">
+      <t-stepper v-model="number" :step="1" :min="0" :max="100" disabled />
+    </t-cell>
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 
-export default defineComponent({
-  setup() {
-    return {
-      number: ref(0),
-    };
-  },
-});
+const number = ref(0);
 </script>
diff --git a/src/stepper/demos/event.vue b/src/stepper/demos/event.vue
index 5cd2dd97..45ac68dc 100644
--- a/src/stepper/demos/event.vue
+++ b/src/stepper/demos/event.vue
@@ -1,38 +1,32 @@
 <template>
-  <div class="cell-base">
-    <t-cell-group title="绑定事件">
-      <t-cell title="标题文字">
+  <div class="stepper-demo">
+    <t-cell title="标题文字（单位）">
+      <template #rightIcon>
         <t-stepper
-          v-model.number="number"
+          v-model="number"
           :step="1"
           :min="0"
           :max="100"
-          :input-width="70"
           @overlimit="onOverlimit"
           @change="onChange"
           @blur="onBlur"
         />
-      </t-cell>
-    </t-cell-group>
+      </template>
+    </t-cell>
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 
-export default defineComponent({
-  setup() {
-    const onChange = ($event: number) => {
-      console.log(`change to ${$event}`);
-    };
-    const onBlur = ($event: number) => {
-      console.log(`onBlur to ${$event}`);
-    };
-    return {
-      number: ref(10),
-      onChange,
-      onBlur,
-    };
-  },
-});
+const number = ref(0);
+const onChange = ($event: number) => {
+  console.log(`change to ${$event}`);
+};
+const onBlur = ($event: number) => {
+  console.log(`blur to ${$event}`);
+};
+const onOverlimit = ($type: string) => {
+  console.log(`onOverlimit ${$type}`);
+};
 </script>
diff --git a/src/stepper/demos/mobile.vue b/src/stepper/demos/mobile.vue
index 8a1b79fe..148aa078 100644
--- a/src/stepper/demos/mobile.vue
+++ b/src/stepper/demos/mobile.vue
@@ -3,93 +3,50 @@
     <h1 class="title">Stepper 步进器</h1>
     <p class="summary">用户通过调整“+”按钮、“-”按钮、数字输入框来调整具体需要的数值，可设置最大值和最小值</p>
     <tdesign-demo-block title="01 类型" summary="基础步进器">
-      <div class="stepper-demo">
-        <t-cell title="标题文字">
-          <t-stepper
-            v-model="number1"
-            :step="1"
-            :min="0"
-            :max="100"
-            @overlimit="onOverlimit"
-            @change="onChange"
-            @blur="onBlur"
-          />
-        </t-cell>
-      </div>
+      <baseDemo />
     </tdesign-demo-block>
     <tdesign-demo-block summary="带单位步进器">
-      <div class="stepper-demo">
-        <t-cell title="标题文字（单位）">
-          <template #rightIcon>
-            <t-stepper v-model.number="number2" :step="1" :min="0" :max="100" />
-          </template>
-        </t-cell>
-      </div>
+      <eventDemo />
     </tdesign-demo-block>
     <tdesign-demo-block summary="纯步进器">
       <div style="margin-left: 16px">
-        <t-stepper v-model.number="number3" theme="grey" :step="1" :min="0" :max="100" />
+        <t-stepper v-model="number1" theme="grey" :step="1" :min="0" :max="100" />
       </div>
     </tdesign-demo-block>
     <tdesign-demo-block title="02 类型" summary="步进器状态">
-      <div class="stepper-demo">
-        <t-cell title="禁用">
-          <t-stepper v-model.number="number4" :step="1" :min="0" :max="100" disabled />
-        </t-cell>
-      </div>
-      <div class="stepper-demo">
-        <t-cell title="禁用单位">
-          <t-stepper v-model.number="number5" :step="1" :min="0" :max="100" disabled />
-        </t-cell>
-      </div>
+      <disableDemo />
+      <disableInputDemo />
+
       <div class="stepper-demo">
         <t-cell title="最大值">
-          <t-stepper v-model.number="number6" :step="1" :min="0" :max="999" />
+          <t-stepper v-model="number2" :step="1" :min="0" :max="999" />
         </t-cell>
       </div>
       <div class="stepper-demo">
         <t-cell title="最小值">
-          <t-stepper v-model.number="number7" :step="1" :min="0" :max="999" />
+          <t-stepper v-model="number3" :step="1" :min="0" :max="999" />
         </t-cell>
       </div>
       <div class="stepper-inline-demo">
-        <t-stepper v-model.number="number3" theme="grey" :step="1" :min="0" :max="100" disabled />
-        <t-stepper v-model.number="number6" theme="grey" :step="1" :min="0" :max="100" />
-        <t-stepper v-model.number="number7" theme="grey" :step="1" :min="0" :max="100" />
+        <t-stepper v-model="number4" theme="grey" :step="1" :min="0" :max="100" disabled />
+        <t-stepper v-model="number5" theme="grey" :step="1" :min="0" :max="100" />
+        <t-stepper v-model="number6" theme="grey" :step="1" :min="0" :max="100" />
       </div>
     </tdesign-demo-block>
   </div>
 </template>
 
-<script lang="ts">
-import { reactive, toRefs, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
+import baseDemo from './base.vue';
+import disableDemo from './disabled.vue';
+import disableInputDemo from './disable-input.vue';
+import eventDemo from './event.vue';
 
-export default defineComponent({
-  setup() {
-    const state = reactive({
-      number1: 1,
-      number2: 99,
-      number3: 3,
-      number4: 0,
-      number5: 0,
-      number6: 999,
-      number7: 0,
-    });
-    const onChange = ($event: number) => {
-      console.log(`change to ${$event}`);
-    };
-    const onBlur = ($event: number) => {
-      console.log(`blur to ${$event}`);
-    };
-    const onOverlimit = ($type: string) => {
-      console.log(`onOverlimit ${$type}`);
-    };
-    return {
-      onChange,
-      onBlur,
-      onOverlimit,
-      ...toRefs(state),
-    };
-  },
-});
+const number1 = ref(3);
+const number2 = ref(999);
+const number3 = ref(0);
+const number4 = ref(3);
+const number5 = ref(999);
+const number6 = ref(0);
 </script>
diff --git a/src/stepper/stepper.vue b/src/stepper/stepper.vue
index 4d59cde4..418943f9 100644
--- a/src/stepper/stepper.vue
+++ b/src/stepper/stepper.vue
@@ -8,10 +8,10 @@
       :class="`${name}__input`"
       type="tel"
       :style="inputStyle"
-      pattern="[0-9]*"
       :disabled="disableInput || disabled"
       :readonly="disableInput"
       @blur="handleBlur"
+      @input="handleInput"
     />
     <div :class="[`${name}__plus`, `${disabled || stepperValue >= max ? 't-is-disabled' : ''}`]" @click="plusValue">
       <add-icon :class="`${name}__icon`" />
@@ -20,7 +20,7 @@
 </template>
 
 <script lang="ts">
-import { toRefs, computed, reactive, defineComponent, SetupContext } from 'vue';
+import { toRefs, computed, defineComponent, SetupContext, nextTick } from 'vue';
 import { AddIcon, RemoveIcon } from 'tdesign-icons-vue-next';
 import config from '../config';
 import StepperProps from './props';
@@ -43,41 +43,59 @@ export default defineComponent({
     const [stepperValue] = useDefault<number, TdStepperProps>(props, context.emit, 'value', 'change');
     const emitEvent = useEmitEvent(props, context.emit);
 
-    const { min, max, inputWidth, theme } = toRefs(props);
-    const isPureMode = theme.value === 'grey';
-    const inputStyle = inputWidth ? { width: `${inputWidth.value}px` } : '';
+    const { min, max, step, inputWidth, theme, disabled } = toRefs(props);
+    const isPureMode = computed(() => theme.value === 'grey');
+    const inputStyle = computed(() => (inputWidth ? { width: `${inputWidth.value}px` } : ''));
+
+    const isDisabled = (type: 'minus' | 'plus') => {
+      if (disabled.value) return true;
+      if (type === 'minus' && stepperValue.value <= min.value) {
+        return true;
+      }
+      if (type === 'plus' && stepperValue.value >= max.value) {
+        return true;
+      }
+      return false;
+    };
+
+    const formatValue = (value: number) =>
+      Math.max(Math.min(max.value, value, Number.MAX_SAFE_INTEGER), min.value, Number.MIN_SAFE_INTEGER);
+
+    const updateValue = (value: number) => {
+      stepperValue.value = formatValue(value);
+    };
 
-    const format = (val: number) =>
-      Math.min(Math.max(min.value, val, Number.MIN_SAFE_INTEGER), max.value, Number.MAX_SAFE_INTEGER);
     const plusValue = () => {
-      if (stepperValue.value + props.step > props.max || props.disabled) return;
-      stepperValue.value += props.step;
+      if (isDisabled('plus')) {
+        emitEvent('overlimit', 'plus');
+        return;
+      }
+      updateValue(Number(stepperValue.value) + step.value);
     };
+
     const minusValue = () => {
-      if (stepperValue.value - props.step < props.min || props.disabled) return;
-      stepperValue.value -= props.step;
-    };
-    const changeValue = (e: Event) => {
-      const value = (e.target as HTMLTextAreaElement).value.split('.')[0].replace(/[^-0-9]/g, '');
-      if (value.trim() === '') {
-        stepperValue.value = 0;
-      } else {
-        handleOverlimit(Number(value));
-        stepperValue.value = format(Number(value));
+      if (isDisabled('minus')) {
+        emitEvent('overlimit', 'minus');
+        return;
       }
+      updateValue(Number(stepperValue.value) - step.value);
+    };
+
+    const handleInput = (e: Event) => {
+      handleChange(e);
     };
+
+    const handleChange = (e: Event) => {
+      const value = (e.target as HTMLTextAreaElement).value.match(/^\d+\.\d+|^\d+/g);
+      if (isNaN(Number(value))) return;
+      const formattedValue = formatValue(Number(value));
+      updateValue(Number(formattedValue));
+    };
+
     const handleBlur = (e: FocusEvent) => {
-      changeValue(e);
+      handleChange(e);
       emitEvent('blur', stepperValue.value);
     };
-    const handleOverlimit = (value: number) => {
-      if (value < Math.max(min.value, Number.MIN_SAFE_INTEGER)) {
-        emitEvent('overlimit', 'minus');
-      }
-      if (value > Math.min(max.value, Number.MAX_SAFE_INTEGER)) {
-        emitEvent('overlimit', 'plus');
-      }
-    };
 
     return {
       name,
@@ -85,7 +103,8 @@ export default defineComponent({
       minusValue,
       stepperValue,
       plusValue,
-      changeValue,
+      handleInput,
+      handleChange,
       inputStyle,
       handleBlur,
       isPureMode,


Test patch:
diff --git a/site/web/test-coverage.js b/site/web/test-coverage.js
index e0da53e3..1f662e7e 100644
--- a/site/web/test-coverage.js
+++ b/site/web/test-coverage.js
@@ -1,1 +1,1 @@
-module.exports = {"button":{"statements":"98.57%","branches":"58.82%","functions":"100%","lines":"98.57%"},"checkbox":{"statements":"93.47%","branches":"75.8%","functions":"80%","lines":"93.47%"},"drawer":{"statements":"100%","branches":"93.75%","functions":"100%","lines":"100%"},"fab":{"statements":"100%","branches":"100%","functions":"100%","lines":"100%"},"overlay":{"statements":"94.68%","branches":"100%","functions":"60%","lines":"94.68%"},"popup":{"statements":"99%","branches":"92.3%","functions":"88.88%","lines":"99%"},"shared":{"statements":"70.58%","branches":"61.11%","functions":"25%","lines":"70.58%"},"switch":{"statements":"98.05%","branches":"92.85%","functions":"100%","lines":"98.05%"}}
\ No newline at end of file
+module.exports = {"button":{"statements":"98.57%","branches":"58.82%","functions":"100%","lines":"98.57%"},"checkbox":{"statements":"93.47%","branches":"75.8%","functions":"80%","lines":"93.47%"},"drawer":{"statements":"100%","branches":"93.75%","functions":"100%","lines":"100%"},"fab":{"statements":"100%","branches":"100%","functions":"100%","lines":"100%"},"overlay":{"statements":"92.55%","branches":"100%","functions":"20%","lines":"92.55%"},"popup":{"statements":"95%","branches":"64.7%","functions":"55.55%","lines":"95%"},"shared":{"statements":"70.58%","branches":"61.11%","functions":"25%","lines":"70.58%"},"stepper":{"statements":"100%","branches":"93.18%","functions":"100%","lines":"100%"},"switch":{"statements":"98.05%","branches":"92.85%","functions":"100%","lines":"98.05%"}}
\ No newline at end of file
diff --git a/src/stepper/__test__/__snapshots__/demo.test.jsx.snap b/src/stepper/__test__/__snapshots__/demo.test.jsx.snap
index 2de506d9..ea642a0b 100644
--- a/src/stepper/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/stepper/__test__/__snapshots__/demo.test.jsx.snap
@@ -2,369 +2,292 @@
 
 exports[`Stepper > Stepper baseVue demo works fine 1`] = `
 <div
-  class="cell-base"
+  class="stepper-demo"
 >
   <div
-    class="t-cell-group"
+    class="t-cell t-cell--middle t-cell--bordered"
   >
     <div
-      class="t-cell-group__title"
+      class="t-cell__left-icon"
     >
-      
-      基础步进器
-      
+      <!--v-if-->
+      <!--v-if-->
+    </div>
+    <div
+      class="t-cell__title"
+    >
+      标题文字
+      <!--v-if-->
+      <!--v-if-->
     </div>
     <div
-      class="t-cell-group__container"
+      class="t-cell__note"
     >
       
       <div
-        class="t-cell t-cell--middle t-cell--bordered"
+        class="t-stepper"
       >
         <div
-          class="t-cell__left-icon"
-        >
-          <!--v-if-->
-          <!--v-if-->
-        </div>
-        <div
-          class="t-cell__title"
+          class="t-stepper__minus t-is-disabled"
         >
-          标题文字
-          <!--v-if-->
-          <!--v-if-->
+          <svg
+            class="t-icon t-icon-remove t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
+          >
+            <path
+              d="M3.5 7.35h9v1.3h-9v-1.3z"
+              fill="currentColor"
+              fill-opacity="0.9"
+            />
+          </svg>
         </div>
+        <input
+          class="t-stepper__input"
+          type="tel"
+        />
         <div
-          class="t-cell__note"
+          class="t-stepper__plus"
         >
-          
-          <div
-            class="t-stepper"
-            modelmodifiers="[object Object]"
+          <svg
+            class="t-icon t-icon-add t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
           >
-            <div
-              class="t-stepper__minus"
-            >
-              <svg
-                class="t-icon t-icon-remove t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M3.5 7.35h9v1.3h-9v-1.3z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-            <input
-              class="t-stepper__input"
-              pattern="[0-9]*"
-              type="tel"
+            <path
+              d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
+              fill="currentColor"
+              fill-opacity="0.9"
             />
-            <div
-              class="t-stepper__plus"
-            >
-              <svg
-                class="t-icon t-icon-add t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-          </div>
-          
+          </svg>
         </div>
-        <!--v-if-->
       </div>
       
     </div>
+    <!--v-if-->
   </div>
 </div>
 `;
 
 exports[`Stepper > Stepper disableInputVue demo works fine 1`] = `
 <div
-  class="cell-base"
+  class="stepper-demo"
 >
   <div
-    class="t-cell-group"
+    class="t-cell t-cell--middle t-cell--bordered"
   >
     <div
-      class="t-cell-group__title"
+      class="t-cell__left-icon"
+    >
+      <!--v-if-->
+      <!--v-if-->
+    </div>
+    <div
+      class="t-cell__title"
     >
-      
       禁止输入
-      
+      <!--v-if-->
+      <!--v-if-->
     </div>
     <div
-      class="t-cell-group__container"
+      class="t-cell__note"
     >
       
       <div
-        class="t-cell t-cell--middle t-cell--bordered"
+        class="t-stepper"
       >
         <div
-          class="t-cell__left-icon"
-        >
-          <!--v-if-->
-          <!--v-if-->
-        </div>
-        <div
-          class="t-cell__title"
+          class="t-stepper__minus t-is-disabled"
         >
-          禁止输入
-          <!--v-if-->
-          <!--v-if-->
+          <svg
+            class="t-icon t-icon-remove t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
+          >
+            <path
+              d="M3.5 7.35h9v1.3h-9v-1.3z"
+              fill="currentColor"
+              fill-opacity="0.9"
+            />
+          </svg>
         </div>
+        <input
+          class="t-stepper__input"
+          disabled=""
+          readonly=""
+          type="tel"
+        />
         <div
-          class="t-cell__note"
+          class="t-stepper__plus"
         >
-          
-          <div
-            class="t-stepper"
-            modelmodifiers="[object Object]"
+          <svg
+            class="t-icon t-icon-add t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
           >
-            <div
-              class="t-stepper__minus"
-            >
-              <svg
-                class="t-icon t-icon-remove t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M3.5 7.35h9v1.3h-9v-1.3z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-            <input
-              class="t-stepper__input"
-              disabled=""
-              pattern="[0-9]*"
-              readonly=""
-              type="tel"
+            <path
+              d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
+              fill="currentColor"
+              fill-opacity="0.9"
             />
-            <div
-              class="t-stepper__plus"
-            >
-              <svg
-                class="t-icon t-icon-add t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-          </div>
-          
+          </svg>
         </div>
-        <!--v-if-->
       </div>
       
     </div>
+    <!--v-if-->
   </div>
 </div>
 `;
 
 exports[`Stepper > Stepper disabledVue demo works fine 1`] = `
 <div
-  class="cell-base"
+  class="stepper-demo"
 >
   <div
-    class="t-cell-group"
+    class="t-cell t-cell--middle t-cell--bordered"
   >
     <div
-      class="t-cell-group__title"
+      class="t-cell__left-icon"
     >
-      
-      禁用状态
-      
+      <!--v-if-->
+      <!--v-if-->
     </div>
     <div
-      class="t-cell-group__container"
+      class="t-cell__title"
+    >
+      禁用
+      <!--v-if-->
+      <!--v-if-->
+    </div>
+    <div
+      class="t-cell__note"
     >
       
       <div
-        class="t-cell t-cell--middle t-cell--bordered"
+        class="t-stepper t-is-disabled"
       >
         <div
-          class="t-cell__left-icon"
-        >
-          <!--v-if-->
-          <!--v-if-->
-        </div>
-        <div
-          class="t-cell__title"
+          class="t-stepper__minus t-is-disabled"
         >
-          失效
-          <!--v-if-->
-          <!--v-if-->
+          <svg
+            class="t-icon t-icon-remove t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
+          >
+            <path
+              d="M3.5 7.35h9v1.3h-9v-1.3z"
+              fill="currentColor"
+              fill-opacity="0.9"
+            />
+          </svg>
         </div>
+        <input
+          class="t-stepper__input"
+          disabled=""
+          type="tel"
+        />
         <div
-          class="t-cell__note"
+          class="t-stepper__plus t-is-disabled"
         >
-          
-          <div
-            class="t-stepper t-is-disabled"
-            modelmodifiers="[object Object]"
+          <svg
+            class="t-icon t-icon-add t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
           >
-            <div
-              class="t-stepper__minus t-is-disabled"
-            >
-              <svg
-                class="t-icon t-icon-remove t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M3.5 7.35h9v1.3h-9v-1.3z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-            <input
-              class="t-stepper__input"
-              disabled=""
-              pattern="[0-9]*"
-              type="tel"
+            <path
+              d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
+              fill="currentColor"
+              fill-opacity="0.9"
             />
-            <div
-              class="t-stepper__plus t-is-disabled"
-            >
-              <svg
-                class="t-icon t-icon-add t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-          </div>
-          
+          </svg>
         </div>
-        <!--v-if-->
       </div>
       
     </div>
+    <!--v-if-->
   </div>
 </div>
 `;
 
 exports[`Stepper > Stepper eventVue demo works fine 1`] = `
 <div
-  class="cell-base"
+  class="stepper-demo"
 >
   <div
-    class="t-cell-group"
+    class="t-cell t-cell--middle t-cell--bordered"
   >
     <div
-      class="t-cell-group__title"
+      class="t-cell__left-icon"
     >
-      
-      绑定事件
-      
+      <!--v-if-->
+      <!--v-if-->
     </div>
     <div
-      class="t-cell-group__container"
+      class="t-cell__title"
+    >
+      标题文字（单位）
+      <!--v-if-->
+      <!--v-if-->
+    </div>
+    <!--v-if-->
+    <div
+      class="t-cell__right-icon"
     >
       
       <div
-        class="t-cell t-cell--middle t-cell--bordered"
+        class="t-stepper"
       >
         <div
-          class="t-cell__left-icon"
-        >
-          <!--v-if-->
-          <!--v-if-->
-        </div>
-        <div
-          class="t-cell__title"
+          class="t-stepper__minus t-is-disabled"
         >
-          标题文字
-          <!--v-if-->
-          <!--v-if-->
+          <svg
+            class="t-icon t-icon-remove t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
+          >
+            <path
+              d="M3.5 7.35h9v1.3h-9v-1.3z"
+              fill="currentColor"
+              fill-opacity="0.9"
+            />
+          </svg>
         </div>
+        <input
+          class="t-stepper__input"
+          type="tel"
+        />
         <div
-          class="t-cell__note"
+          class="t-stepper__plus"
         >
-          
-          <div
-            class="t-stepper"
-            modelmodifiers="[object Object]"
+          <svg
+            class="t-icon t-icon-add t-stepper__icon"
+            fill="none"
+            height="1em"
+            viewBox="0 0 16 16"
+            width="1em"
           >
-            <div
-              class="t-stepper__minus"
-            >
-              <svg
-                class="t-icon t-icon-remove t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M3.5 7.35h9v1.3h-9v-1.3z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-            <input
-              class="t-stepper__input"
-              pattern="[0-9]*"
-              style="width: 70px;"
-              type="tel"
+            <path
+              d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
+              fill="currentColor"
+              fill-opacity="0.9"
             />
-            <div
-              class="t-stepper__plus"
-            >
-              <svg
-                class="t-icon t-icon-add t-stepper__icon"
-                fill="none"
-                height="1em"
-                viewBox="0 0 16 16"
-                width="1em"
-              >
-                <path
-                  d="M7.35 8.65v3.85h1.3V8.65h3.85v-1.3H8.65V3.5h-1.3v3.85H3.5v1.3h3.85z"
-                  fill="currentColor"
-                  fill-opacity="0.9"
-                />
-              </svg>
-            </div>
-          </div>
-          
+          </svg>
         </div>
-        <!--v-if-->
       </div>
       
     </div>
@@ -427,7 +350,7 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             class="t-stepper"
           >
             <div
-              class="t-stepper__minus"
+              class="t-stepper__minus t-is-disabled"
             >
               <svg
                 class="t-icon t-icon-remove t-stepper__icon"
@@ -445,7 +368,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             </div>
             <input
               class="t-stepper__input"
-              pattern="[0-9]*"
               type="tel"
             />
             <div
@@ -509,10 +431,9 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
           
           <div
             class="t-stepper"
-            modelmodifiers="[object Object]"
           >
             <div
-              class="t-stepper__minus"
+              class="t-stepper__minus t-is-disabled"
             >
               <svg
                 class="t-icon t-icon-remove t-stepper__icon"
@@ -530,7 +451,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             </div>
             <input
               class="t-stepper__input"
-              pattern="[0-9]*"
               type="tel"
             />
             <div
@@ -572,7 +492,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
     >
       <div
         class="t-stepper t-stepper__pure"
-        modelmodifiers="[object Object]"
       >
         <div
           class="t-stepper__minus"
@@ -593,7 +512,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
         </div>
         <input
           class="t-stepper__input"
-          pattern="[0-9]*"
           type="tel"
         />
         <div
@@ -656,7 +574,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
           
           <div
             class="t-stepper t-is-disabled"
-            modelmodifiers="[object Object]"
           >
             <div
               class="t-stepper__minus t-is-disabled"
@@ -678,7 +595,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             <input
               class="t-stepper__input"
               disabled=""
-              pattern="[0-9]*"
               type="tel"
             />
             <div
@@ -719,7 +635,7 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
         <div
           class="t-cell__title"
         >
-          禁用单位
+          禁止输入
           <!--v-if-->
           <!--v-if-->
         </div>
@@ -728,8 +644,7 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
         >
           
           <div
-            class="t-stepper t-is-disabled"
-            modelmodifiers="[object Object]"
+            class="t-stepper"
           >
             <div
               class="t-stepper__minus t-is-disabled"
@@ -751,11 +666,11 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             <input
               class="t-stepper__input"
               disabled=""
-              pattern="[0-9]*"
+              readonly=""
               type="tel"
             />
             <div
-              class="t-stepper__plus t-is-disabled"
+              class="t-stepper__plus"
             >
               <svg
                 class="t-icon t-icon-add t-stepper__icon"
@@ -802,7 +717,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
           
           <div
             class="t-stepper"
-            modelmodifiers="[object Object]"
           >
             <div
               class="t-stepper__minus"
@@ -823,7 +737,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             </div>
             <input
               class="t-stepper__input"
-              pattern="[0-9]*"
               type="tel"
             />
             <div
@@ -874,7 +787,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
           
           <div
             class="t-stepper"
-            modelmodifiers="[object Object]"
           >
             <div
               class="t-stepper__minus t-is-disabled"
@@ -895,7 +807,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
             </div>
             <input
               class="t-stepper__input"
-              pattern="[0-9]*"
               type="tel"
             />
             <div
@@ -926,7 +837,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
     >
       <div
         class="t-stepper t-is-disabled t-stepper__pure"
-        modelmodifiers="[object Object]"
       >
         <div
           class="t-stepper__minus t-is-disabled"
@@ -948,7 +858,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
         <input
           class="t-stepper__input"
           disabled=""
-          pattern="[0-9]*"
           type="tel"
         />
         <div
@@ -971,7 +880,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
       </div>
       <div
         class="t-stepper t-stepper__pure"
-        modelmodifiers="[object Object]"
       >
         <div
           class="t-stepper__minus"
@@ -992,7 +900,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
         </div>
         <input
           class="t-stepper__input"
-          pattern="[0-9]*"
           type="tel"
         />
         <div
@@ -1015,7 +922,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
       </div>
       <div
         class="t-stepper t-stepper__pure"
-        modelmodifiers="[object Object]"
       >
         <div
           class="t-stepper__minus t-is-disabled"
@@ -1036,7 +942,6 @@ exports[`Stepper > Stepper mobileVue demo works fine 1`] = `
         </div>
         <input
           class="t-stepper__input"
-          pattern="[0-9]*"
           type="tel"
         />
         <div
diff --git a/src/stepper/__test__/index.test.jsx b/src/stepper/__test__/index.test.jsx
new file mode 100644
index 00000000..f977ab0f
--- /dev/null
+++ b/src/stepper/__test__/index.test.jsx
@@ -0,0 +1,159 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi } from 'vitest';
+import { nextTick } from 'vue';
+import Stepper from '../stepper.vue';
+import { ref } from 'vue';
+
+describe('stepper', () => {
+  describe('props', () => {
+    it(': disabled', async () => {
+      const wrapper = mount(Stepper, {
+          props: {
+            disabled: true,
+          },
+      });
+      expect(wrapper.classes()).toContain('t-is-disabled');
+
+      // 更新已挂载组件的 props
+      await wrapper.setProps({
+        disabled: false,
+      })
+      expect(wrapper.classes()).not.toContain('t-is-disabled');
+    });
+
+    it(': input-width', async () => {
+      const wrapper = mount(Stepper, {
+          props: {
+            inputWidth: 80,
+          },
+      });
+      const $input = wrapper.find('.t-stepper__input');
+      expect(getComputedStyle($input.element, null).width).toBe('80px');
+
+      await wrapper.setProps({
+        inputWidth: 100,
+      })
+      expect(getComputedStyle($input.element, null).width).toBe('100px');
+    });
+
+
+    it(': theme', async () => {
+      const wrapper = mount(Stepper, {});
+
+      expect(wrapper.classes()).not.toContain('t-stepper__pure')
+      await wrapper.setProps({
+        theme: 'grey',
+      })
+      expect(wrapper.classes()).toContain('t-stepper__pure')
+    })
+
+    it(': step && max && min', async () => {
+      const onChange = vi.fn();
+
+      const max = ref(3);
+      const min = ref(1);
+      const step = ref(2);
+      const value = ref(2);
+
+      const wrapper = mount({
+        render() {
+          return <Stepper v-model={value.value} step={step.value} min={min.value} max={max.value} onChange={onChange} />
+        }
+      })
+
+      const minusIcon = wrapper.find('.t-stepper__minus');
+      const plusIcon = wrapper.find('.t-stepper__plus');
+      await minusIcon.trigger('click')
+      await nextTick()
+      // value <= min，change 触发， value = min
+      expect(onChange).toBeCalledTimes(1)
+      expect(value.value).toBe(1);
+      expect(minusIcon.classes()).toContain('t-is-disabled')
+
+      await plusIcon.trigger('click')
+      await nextTick()
+      expect(onChange).toBeCalledTimes(2)
+      expect(value.value).toBe(3);
+
+      // 此时 value = max, plusIcon 为禁用态
+      expect(plusIcon.classes()).toContain('t-is-disabled')
+
+    })
+  })
+
+  describe('events', () => {
+    it(': overlimit', async () => {
+      const onChange = vi.fn();
+      const onOverlimit = vi.fn();
+      const onBlur = vi.fn();
+      const max = ref(5);
+      const step = ref(5);
+      const value = ref(2);
+      const wrapper = mount({
+        render() {
+          return <Stepper v-model={value.value} step={step.value} max={max.value} onChange={onChange} onOverlimit={onOverlimit} onBlur={ onBlur } />
+        }
+      })
+      const minusIcon = wrapper.find('.t-stepper__minus');
+      const plusIcon = wrapper.find('.t-stepper__plus');
+      /**
+       * plusIcon
+       */
+      // 当前value < max ,触发 change，
+      await plusIcon.trigger('click')
+      await nextTick()
+      expect(onChange).toBeCalledTimes(1)
+      expect(value.value).toBe(5);
+
+      // 此时 value = max，不触发 change，但触发 overlimit
+      await plusIcon.trigger('click')
+      await nextTick()
+      expect(onChange).toBeCalledTimes(1)
+      expect(onOverlimit).toBeCalledTimes(1)
+      expect(value.value).toBe(5);
+
+      /**
+       * minusIcon
+       */
+      // 此时 value = 5，
+      await minusIcon.trigger('click')
+      await nextTick()
+      expect(value.value).toBe(0);
+      expect(onChange).toBeCalledTimes(2)
+
+      //此时 value = min， 不触发 change，但触发 overlimit
+      await minusIcon.trigger('click')
+      await nextTick()
+      expect(onChange).toBeCalledTimes(2)
+      expect(onOverlimit).toBeCalledTimes(2)
+
+      // 测试 input
+      const $input = wrapper.find('.t-stepper__input');
+
+      $input.trigger('blur')
+      expect(onBlur).toHaveBeenCalled(1)
+    });
+
+    it(': blur', async () => {
+      const onChange = vi.fn();
+      const onBlur = vi.fn();
+      const value = ref(2);
+      const wrapper = mount({
+        render() {
+          return <Stepper v-model={value.value} onChange={onChange} onBlur={ onBlur } />
+        }
+      })
+
+      const $input = wrapper.find('.t-stepper__input');
+
+      // blur
+      $input.trigger('blur')
+      expect(onBlur).toHaveBeenCalled(1)
+
+      // input, 触发 change
+      $input.setValue('10');
+      expect(onChange).toBeCalled(1);
+      expect(value.value).toBe(10);
+    });
+  })
+})