Repository: Tencent/tdesign-mobile-vue (ts)

Issue:
[CountDown] 毫秒级渲染配置无效
### tdesign-mobile-vue 版本

0.10.4

### 重现链接

https://tdesign.tencent.com/mobile-vue/overview

### 重现步骤

props设置millisecond为true

### 期望结果

开启毫秒级渲染

### 实际结果

没有任何效果

### 框架版本

Vue(3.2.25)

### 浏览器版本

Chrome(103.0.5060.114)

### 系统版本

MACOS(12.4)

### Node版本

16.16.9

### 补充说明

_No response_

Pull request:
fix(countdown): fix milisecond problem and add unit test
fix #312

<!--
首先，感谢你的贡献！😄
请阅读并遵循 [TDesign 贡献指南](https://github.com/Tencent/tdesign/blob/main/docs/contributing.md)，填写以下 pull request 的信息。
PR 在维护者审核通过后会合并，谢谢！
-->

### 🤔 这个 PR 的性质是？

- [x] 日常 bug 修复
- [ ] 新特性提交
- [ ] 文档改进
- [ ] 演示代码改进
- [ ] 组件样式/交互改进
- [ ] CI/CD 改进
- [ ] 重构
- [ ] 代码风格优化
- [x] 测试用例
- [ ] 分支合并
- [ ] 其他

### 🔗 相关 Issue

<!--
1. 描述相关需求的来源，如相关的 issue 讨论链接。
-->
[毫秒级渲染配置无效](https://github.com/Tencent/tdesign-mobile-vue/issues/312)

### 💡 需求背景和解决方案

<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->
props传入milisecond为true无效

### 📝 更新日志

<!--
从用户角度描述具体变化，以及可能的 breaking change 和其他风险。
-->

- fix(CountDown): 修复 milisecond 配置无效的问题
- feat(CountDown): 新增单元测试

- [ ] 本条 PR 不需要纳入 Changelog

### ☑️ 请求合并前的自查清单

⚠️ 请自检并全部**勾选全部选项**。⚠️

- [x] 文档已补充或无须补充
- [x] 代码演示已提供或无须提供
- [x] TypeScript 定义已补充或无须补充
- [x] Changelog 已提供或无须提供


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 317284e4..e78aff15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,27 @@ toc: false
 spline: explain
 ---
 
+## 🌈 0.10.6 `2022-09-20` 
+### 🚀 Features
+- `Drawer`: 新增单元测试 @anlyyao ([#311](https://github.com/Tencent/tdesign-mobile-vue/pull/311))
+- `Popup`: 新增单元测试 @anlyyao ([#310](https://github.com/Tencent/tdesign-mobile-vue/pull/310))
+- `Stepper`: 新增单元测试 @anlyyao ([#304](https://github.com/Tencent/tdesign-mobile-vue/pull/304))
+- `Overlay`: 新增单元测试 @HelKyle ([#336](https://github.com/Tencent/tdesign-mobile-vue/pull/336))
+- `BackTop`: 新增单元测试 @HelKyle ([#335](https://github.com/Tencent/tdesign-mobile-vue/pull/335))
+- `Toast`: 新增单元测试 @HelKyle ([#334](https://github.com/Tencent/tdesign-mobile-vue/pull/334))
+- `ActionSheet`: 新增单元测试 @HelKyle ([#319](https://github.com/Tencent/tdesign-mobile-vue/pull/319))
+- `Progress`: 新增单元测试 @HelKyle ([#337](https://github.com/Tencent/tdesign-mobile-vue/pull/337))
+- `ImageViewer`: 新增单元测试 @palmcivet ([#332](https://github.com/Tencent/tdesign-mobile-vue/pull/332))
+- `TabBar`: 新增单元测试 @HelKyle ([#314](https://github.com/Tencent/tdesign-mobile-vue/pull/314))
+- `Dialog`: 新增单元测试 @anlyyao ([#361](https://github.com/Tencent/tdesign-mobile-vue/pull/361))
+### 🐞 Bug Fixes
+- `Stepper`: 修复输入非 `number` 字符时，出现 `NaN` 问题 @anlyyao ([#304](https://github.com/Tencent/tdesign-mobile-vue/pull/304))
+- `Stepper`: 修复输入值能超出 `max` 的问题 @anlyyao ([#304](https://github.com/Tencent/tdesign-mobile-vue/pull/304))
+- `Stepper`: 修复 `value` 值小于 `min` 或超出 `max` 时，不触发 `overlimit` 的问题 @anlyyao ([#304](https://github.com/Tencent/tdesign-mobile-vue/pull/304))
+- `Collapse`: 修复 `headerRightContent` 插槽与文档描述不一致的问题 @isanxia ([#330](https://github.com/Tencent/tdesign-mobile-vue/pull/330))
+### 🚧 Others
+- `官网`: 更新浏览器兼容性说明文档 @anlyyao ([#349](https://github.com/Tencent/tdesign-mobile-vue/pull/349))
+
 ## 🌈 0.10.5 `2022-09-06` 
 ### 🚀 Features
 - `Popup`: 新增 `customStyle` 和 `overlayProps` 属性 @anlyyao ([#296](https://github.com/Tencent/tdesign-mobile-vue/pull/296))
diff --git a/package.json b/package.json
index de497356..23e00d3c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "tdesign-mobile-vue",
   "purename": "tdesign",
-  "version": "0.10.5",
+  "version": "0.10.6",
   "description": "tdesign-mobile-vue",
   "author": "TDesign Group",
   "homepage": "https://tdesign.tencent.com/mobile-vue/",
diff --git a/src/count-down/count-down.vue b/src/count-down/count-down.vue
index 1891565b..7bab8bb9 100644
--- a/src/count-down/count-down.vue
+++ b/src/count-down/count-down.vue
@@ -29,11 +29,9 @@ export default defineComponent({
     ...CountDownProps,
   },
   setup(props) {
-    const { content, size = 'small', theme = 'default', ...other } = props || {};
-    //
-    const { time, showTimes } = useCountDown(other);
+    const { time, showTimes } = useCountDown(props);
     const internalInstance = getCurrentInstance();
-    const hasChinese = /.*[\u4e00-\u9fa5]+.*$/?.test?.(other?.format);
+    const hasChinese = /.*[\u4e00-\u9fa5]+.*$/?.test?.(props?.format);
     const contentLayout = computed(() => renderTNode(internalInstance, 'content'));
     // return
     return {
diff --git a/src/shared/useCountDown/index.ts b/src/shared/useCountDown/index.ts
index 5aea3542..ef4d2472 100644
--- a/src/shared/useCountDown/index.ts
+++ b/src/shared/useCountDown/index.ts
@@ -8,7 +8,7 @@ export function useCountDown(props: TdUseCountDownProps): TdUseCountDown {
   // state
   const fps = ref();
   const count = ref(Number(time));
-  const showTimes = reactive(getShowTimes(getRemainTimes(time), format));
+  const showTimes = reactive(getShowTimes(getRemainTimes(time), format, !!millisecond));
 
   // raf
   const { pause, resume } = useRafFn(
diff --git a/src/shared/useCountDown/utils.ts b/src/shared/useCountDown/utils.ts
index 5361bf8a..a2084aef 100644
--- a/src/shared/useCountDown/utils.ts
+++ b/src/shared/useCountDown/utils.ts
@@ -30,7 +30,17 @@ export const getRemainTimes = (time?: number): TimeData => {
  * @param num
  * @returns
  */
-export const fillZero = (num: number): string | number => (num >= 10 ? num : `0${num}`);
+export const fillZero = (num: number, isMillieconds = false): string | number => {
+  if (isMillieconds) {
+    if (num >= 100) {
+      return num;
+    }
+
+    return num >= 10 ? `0${num}` : `00${num}`;
+  }
+
+  return num >= 10 ? num : `0${num}`;
+};
 
 /**
  * getMark
@@ -45,8 +55,9 @@ export const getMark = (format: string, type: string): string => format?.split?.
  * @param time
  * @returns
  */
-export const getShowTimes = (times: TimeData, format: string): TdUseCountDownShowTimes => {
+export const getShowTimes = (times: TimeData, format: string, milliseconds = false): TdUseCountDownShowTimes => {
   format = (format || 'DD:HH:mm:ss')?.toUpperCase?.();
+  milliseconds && !format.includes(':SSS') && (format = format.concat(':SSS'));
   const showTimes: TdUseCountDownShowTimes = [];
   if (format?.indexOf('DD') > -1) {
     showTimes?.push({
@@ -75,7 +86,7 @@ export const getShowTimes = (times: TimeData, format: string): TdUseCountDownSho
   if (format?.indexOf('SSS') > -1) {
     showTimes?.push({
       mark: getMark(format, 'SSS'),
-      value: fillZero(times?.milliseconds),
+      value: fillZero(times?.milliseconds, true),
     });
   }
 


Test patch:
diff --git a/src/collapse/__test__/index.test.jsx b/src/collapse/__test__/index.test.jsx
new file mode 100644
index 00000000..bb792e58
--- /dev/null
+++ b/src/collapse/__test__/index.test.jsx
@@ -0,0 +1,225 @@
+import { ref, reactive } from 'vue';
+import { mount } from '@vue/test-utils';
+import { Collapse, CollapsePanel } from '../index';
+
+describe('Collapse', () => {
+  describe('collapse props', () => {
+    test('defaultExpandAll', async () => {
+      const defaultExpandAll = ref(false);
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse defaultExpandAll={defaultExpandAll}>
+              <CollapsePanel ref="1" value="1" header="标题1" content="内容1" />
+              <CollapsePanel ref="2" value="2" header="标题2" content="内容2" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel1 = wrapper.findComponent({ ref: '1' });
+      const panel2 = wrapper.findComponent({ ref: '2' });
+
+      expect(panel1.classes()).not.toContain('t-collapse-panel--active');
+      expect(panel2.classes()).not.toContain('t-collapse-panel--active');
+
+      defaultExpandAll.value = true;
+      await wrapper.vm.$nextTick();
+
+      expect(panel1.classes()).toContain('t-collapse-panel--active');
+      expect(panel2.classes()).toContain('t-collapse-panel--active');
+    });
+
+    test('default-value', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse default-value={['1']}>
+              <CollapsePanel ref="1" value="1" header="标题1" content="内容1" />
+              <CollapsePanel ref="2" value="2" header="标题2" content="内容2" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel1 = wrapper.findComponent({ ref: '1' });
+      const panel2 = wrapper.findComponent({ ref: '2' });
+
+      expect(panel1.classes()).toContain('t-collapse-panel--active');
+      expect(panel2.classes()).not.toContain('t-collapse-panel--active');
+    });
+
+    test('value', async () => {
+      const val = ref([]);
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse v-model={val.value}>
+              <CollapsePanel ref="1" value="1" header="标题1" content="内容1" />
+              <CollapsePanel ref="2" value="2" header="标题2" content="内容2" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel1 = wrapper.findComponent({ ref: '1' });
+      const panel2 = wrapper.findComponent({ ref: '2' });
+      await panel1.find('.t-collapse-panel__header').trigger('click');
+
+      expect(val.value).toHaveLength(1);
+      expect(val.value).toContain('1');
+
+      await panel2.find('.t-collapse-panel__header').trigger('click');
+
+      expect(val.value).toHaveLength(2);
+      expect(val.value).toContain('2');
+    });
+
+    test('expandMutex', async () => {
+      const val = ref([]);
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse expandMutex v-model={val.value}>
+              <CollapsePanel ref="1" value="1" header="标题1" content="内容1" />
+              <CollapsePanel ref="2" value="2" header="标题2" content="内容2" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel1 = wrapper.findComponent({ ref: '1' });
+      const panel2 = wrapper.findComponent({ ref: '2' });
+      await panel1.find('.t-collapse-panel__header').trigger('click');
+
+      expect(val.value).toHaveLength(1);
+      expect(val.value).toContain('1');
+
+      await panel2.find('.t-collapse-panel__header').trigger('click');
+
+      expect(val.value).toHaveLength(1);
+      expect(val.value).toContain('2');
+    });
+
+    test('onChange', async () => {
+      const handleChange = vi.fn();
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse onChange={handleChange}>
+              <CollapsePanel ref="1" value="1" header="标题" content="内容" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel = wrapper.findComponent({ ref: '1' });
+      await panel.find('.t-collapse-panel__header').trigger('click')
+
+      expect(handleChange).toHaveBeenCalled();
+    });
+
+    test('disabled', async () => {
+      const disabled = ref(false);
+      const handleChange = vi.fn();
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse disabled={disabled.value} onChange={handleChange}>
+              <CollapsePanel ref="1" value="1" header="标题" content="内容" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel = wrapper.findComponent({ ref: '1' });
+      await panel.find('.t-collapse-panel__header').trigger('click');
+
+      expect(handleChange).toHaveBeenCalled();
+
+      disabled.value = true;
+      await wrapper.vm.$nextTick();
+      await panel.find('.t-collapse-panel__header').trigger('click');
+
+      expect(handleChange).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('collapse-panel props', () => {
+    test('header and content', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse>
+              <CollapsePanel ref="1" value="1" header="标题" content="内容" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel = wrapper.findComponent({ ref: '1' });
+
+      expect(panel.find('.t-collapse-panel__title').text()).toBe('标题');
+      expect(panel.find('.t-collapse-panel__body').text()).toBe('内容');
+    });
+
+    test('disabled', async () => {
+      const handleChange = vi.fn();
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse onChange={handleChange}>
+              <CollapsePanel ref="1" value="1" disabled header="标题" content="内容" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel = wrapper.findComponent({ ref: '1' });
+
+      await panel.find('.t-collapse-panel__header').trigger('click')
+
+      expect(handleChange).not.toHaveBeenCalled();
+    });
+
+    test('headerRightContent', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse>
+              <CollapsePanel ref="1" value="1" headerRightContent="操作" header="标题" content="内容" />
+            </Collapse>
+          );
+        },
+      });
+
+      const panel = wrapper.findComponent({ ref: '1' });
+
+      expect(panel.find('.t-collapse-panel__header-right').text()).toBe('操作');
+    });
+
+    test('slot', async () => {
+      const wrapper = mount({
+        setup() {
+          return () => (
+            <Collapse>
+              <CollapsePanel ref="1" value="1">
+                {{
+                  header: () => <h4>标题</h4>,
+                  headerRightContent: () => <span>操作</span>,
+                  default: () => <div>内容</div>,
+                }}
+              </CollapsePanel>
+            </Collapse>
+          );
+        },
+      });
+
+      const panel = wrapper.findComponent({ ref: '1' });
+
+      expect(panel.find('.t-collapse-panel__title > h4').html()).toBe('<h4>标题</h4>');
+      expect(panel.find('.t-collapse-panel__header-extra > span').html()).toBe('<span>操作</span>');
+      expect(panel.find('.t-collapse-panel__body > div').html()).toBe('<div>内容</div>');
+    });
+  });
+});
diff --git a/src/count-down/__test__/index.test.jsx b/src/count-down/__test__/index.test.jsx
new file mode 100644
index 00000000..4be3bebc
--- /dev/null
+++ b/src/count-down/__test__/index.test.jsx
@@ -0,0 +1,103 @@
+import { nextTick } from 'vue';
+import { describe, it, expect, vi } from 'vitest';
+import { mount } from '@vue/test-utils';
+import CountDown from '../count-down.vue';
+
+const sleep = (duration) =>
+  new Promise((resolve) =>
+    setTimeout(() => {
+      resolve();
+    }, duration),
+  );
+
+describe('countdown.vue', async () => {
+  describe('props', async () => {
+    it('time', async () => {
+      const wrapper = mount(<CountDown time={2000}></CountDown>);
+      expect(wrapper.text()).toContain('00:00:02');
+      await sleep(1000);
+      expect(wrapper.text()).toContain('00:00:01');
+      await sleep(500);
+      expect(wrapper.text()).toContain('00:00:00');
+      await sleep(500);
+      await nextTick();
+      expect(wrapper.text()).toContain('default');
+    });
+
+    it('content', async () => {
+      const wrapper = mount(<CountDown time={1000} content="ok"></CountDown>);
+      await sleep(1500);
+      await nextTick();
+      expect(wrapper.text()).toContain('ok');
+
+      await wrapper.setProps({ content: 'change' });
+      expect(wrapper.text()).toContain('change');
+    });
+
+    it('format', async () => {
+      const wrapper = mount(<CountDown time={100000} format="mm:ss"></CountDown>);
+      expect(wrapper.text()).not.toContain('00:01:40');
+      expect(wrapper.text()).toContain('01:40');
+
+      const wrapper2 = mount(<CountDown time={100000} format="ss"></CountDown>);
+      expect(wrapper2.text()).not.toContain(':40');
+      expect(wrapper2.text()).toContain('40');
+
+      const wrapper3 = mount(<CountDown time={100000} format="中文"></CountDown>);
+      expect(wrapper3.find('.t-countdown---split-with-unit').exists()).toBe(false);
+
+      const wrapper4 = mount(<CountDown></CountDown>);
+      expect(wrapper4).toBeTruthy();
+    });
+
+    it('millisecond', async () => {
+      const wrapper = mount(<CountDown time={100000} millisecond={true}></CountDown>);
+      expect(wrapper.text()).toContain('00:01:40:000');
+    });
+
+    it('autoStart', async () => {
+      const wrapper = mount(<CountDown time={100000} autoStart={false}></CountDown>);
+      await sleep(1000);
+      expect(wrapper.text()).toContain('00:01:40');
+    });
+
+    it('size', async () => {
+      const wrapper = mount(<CountDown time={100000}></CountDown>);
+      expect(wrapper.find('.t-countdown--small').exists()).toBe(true);
+    });
+
+    it('theme', async () => {
+      const wrapper = mount(<CountDown time={100000}></CountDown>);
+      expect(wrapper.find('.t-countdown--default').exists()).toBe(true);
+    });
+  });
+
+  describe('events', async () => {
+    it('change', async () => {
+      const onChange = vi.fn();
+      const wrapper = mount(<CountDown time={2000} onChange={onChange}></CountDown>);
+      await sleep(1000);
+      expect(onChange).toHaveBeenCalled();
+    });
+
+    it('finish', async () => {
+      const onFinish = vi.fn();
+      const wrapper = mount(<CountDown time={1000} onFinish={onFinish}></CountDown>);
+      await sleep(1500);
+      expect(onFinish).toHaveBeenCalled();
+    });
+  });
+
+  describe('slots', async () => {
+    it('content', async () => {
+      const temp = <div>测试</div>;
+      const wrapper = mount(<CountDown time={1000}></CountDown>, {
+        slots: {
+          content: temp,
+        },
+      });
+      await sleep(1500);
+      expect(wrapper.text()).toContain('测试');
+    });
+  });
+});
diff --git a/src/dialog/__test__/index.test.jsx b/src/dialog/__test__/index.test.jsx
new file mode 100644
index 00000000..5e83cecb
--- /dev/null
+++ b/src/dialog/__test__/index.test.jsx
@@ -0,0 +1,183 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi } from 'vitest';
+import { nextTick } from 'vue';
+import Dialog from '../dialog.vue';
+import Button from '../../button/button.vue';
+
+describe('dialog', () => {
+  describe('props', () => {
+    it(': visible	', async () => {
+      const visible = true;
+      const title = 'this is a title';
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          title,
+        },
+      });
+
+      expect(wrapper.find('.t-overlay').isVisible()).toBe(true)
+      expect(wrapper.find('.t-dialog__header').text()).toBe(title);
+      await wrapper.setProps({
+        visible: false,
+      })
+      expect(wrapper.find('.t-overlay').isVisible()).toBe(false)
+
+    })
+
+    it(': title', async () => {
+      const visible = true;
+      const title = 'old title';
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          title,
+        },
+      });
+      expect(wrapper.find('.t-dialog__header').text()).toBe(title);
+      const newTitle = 'new title'
+      await wrapper.setProps({
+        title: newTitle
+      })
+      expect(wrapper.find('.t-dialog__header').text()).toBe(newTitle);
+    })
+
+    it(': content', async () => {
+      const visible = true;
+      const title = 'this is title';
+      const content = 'content is string';
+
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          title,
+          content,
+        }
+      });
+
+      expect(wrapper.find('.t-dialog__body').text()).toBe(content);
+      const newContent = 'new content';
+      await wrapper.setProps({
+        content: newContent
+      })
+      expect(wrapper.find('.t-dialog__body').text()).toBe(newContent);
+    });
+
+    it(': buttonLayout', async () => {
+      const visible = true;
+      const selector = 't-dialog__vertical-footer';
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+        },
+      });
+      expect(wrapper.find('.t-dialog__footer').classes().includes(selector)).toBeFalsy();
+      await wrapper.setProps({
+        buttonLayout: 'vertical'
+      })
+      expect(wrapper.find('.t-dialog__footer').classes().includes(selector)).toBeTruthy();
+    })
+
+    it(': width', async () => {
+      const visible = true;
+      // width 为 string 类型
+      const width = '80%';
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          width,
+        },
+      });
+      expect(wrapper.find('.t-dialog').attributes('style').includes(`width: ${width}`)).toBeTruthy();
+      // width 为 number 类型
+      const newWidth = 320;
+      await wrapper.setProps({
+        width: newWidth
+      })
+      expect(wrapper.find('.t-dialog').attributes('style').includes(`width: ${newWidth}px`)).toBeTruthy();
+    })
+
+    it(': zIndex', async () => {
+      const visible = true;
+      const zIndex = 11500;
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          zIndex: zIndex,
+        },
+      });
+      // expect(wrapper).toMatchSnapshot()
+      expect(wrapper.find('.t-dialog').attributes('style').includes(`z-index: ${zIndex}`)).toBeTruthy();
+      const newZIndex = 12000;
+      await wrapper.setProps({
+        zIndex: newZIndex
+      })
+      expect(wrapper.find('.t-dialog').attributes('style').includes(`z-index: ${newZIndex}`)).toBeTruthy();
+    })
+
+    it(': closeOnOverlayClick', async () => {
+      let triggerOrigin = {};
+      const onClose = vi.fn((trigger) => {
+        triggerOrigin = trigger;
+      });
+      const onOverlayClick = vi.fn();
+      const closeOverlayClick = true;
+      const visible = true;
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          closeOverlayClick,
+          onClose,
+          onOverlayClick
+        },
+      });
+
+      const $overlay = wrapper.findComponent({ name: 't-overlay' })
+      expect($overlay.exists()).toBeTruthy();
+      $overlay.vm.$emit('click');
+      await nextTick();
+      expect(onClose).toBeCalledTimes(1)
+      expect(onOverlayClick).toBeCalledTimes(1)
+      expect(triggerOrigin).toBe('overlay')
+    })
+
+  });
+
+  describe('event', () => {
+    it(': cancel && confirm', async () => {
+      let triggerOrigin = {};
+      const visible = true;
+      const cancelBtn = 'cancel';
+      const confirmBtn = 'confirm';
+      const title = 'this is a title';
+      const onClose = vi.fn((trigger) => {
+        triggerOrigin = trigger;
+      });
+      const onCancel = vi.fn();
+      const onConfirm = vi.fn();
+      const wrapper = mount(Dialog, {
+        props: {
+          visible,
+          title,
+          cancelBtn,
+          confirmBtn,
+          onCancel,
+          onConfirm,
+          onClose
+        },
+      });
+      // expect(wrapper).toMatchSnapshot()
+      // footer
+      const $buttons = wrapper.findAllComponents(Button);
+      expect($buttons).toHaveLength(2);
+      expect($buttons.at(0).text()).toBe(cancelBtn);
+      expect($buttons.at(1).text()).toBe(confirmBtn);
+      $buttons.at(0).trigger('click');
+      expect(onClose).toBeCalledTimes(1)
+      expect(triggerOrigin).toBe('cancel')
+      expect(onCancel).toBeCalledTimes(1)
+      $buttons.at(1).trigger('click');
+      expect(onConfirm).toBeCalledTimes(1)
+    });
+  });
+})