From 63df7df41b503bd01aac595a70a1419e8f3affcf Mon Sep 17 00:00:00 2001 From: Dubhe Date: Thu, 18 Sep 2025 21:35:09 +0800 Subject: [PATCH 1/2] test(action-sheet): add unit test --- site/test-coverage.js | 6 +- .../__tests__/action-sheet.test.tsx | 409 ++++++++++++++++++ 2 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 src/action-sheet/__tests__/action-sheet.test.tsx diff --git a/site/test-coverage.js b/site/test-coverage.js index 6526c69fc..b234cee92 100644 --- a/site/test-coverage.js +++ b/site/test-coverage.js @@ -1,5 +1,5 @@ module.exports = { - actionSheet: { statements: '5.26%', branches: '0%', functions: '0%', lines: '5.35%' }, + actionSheet: { statements: '100%', branches: '93.1%', functions: '100%', lines: '100%' }, avatar: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, backTop: { statements: '11.9%', branches: '0%', functions: '0%', lines: '12.82%' }, badge: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, @@ -24,7 +24,7 @@ module.exports = { form: { statements: '2.8%', branches: '0%', functions: '0%', lines: '2.96%' }, grid: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, guide: { statements: '3.46%', branches: '0%', functions: '0%', lines: '3.77%' }, - hooks: { statements: '69.04%', branches: '34.32%', functions: '71.87%', lines: '70%' }, + hooks: { statements: '73.8%', branches: '41.79%', functions: '75%', lines: '74.16%' }, image: { statements: '97.72%', branches: '100%', functions: '92.3%', lines: '97.61%' }, imageViewer: { statements: '8.47%', branches: '2.87%', functions: '0%', lines: '8.84%' }, indexes: { statements: '95.65%', branches: '69.81%', functions: '100%', lines: '96.94%' }, @@ -55,7 +55,7 @@ module.exports = { steps: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, sticky: { statements: '7.14%', branches: '0%', functions: '0%', lines: '7.27%' }, swipeCell: { statements: '4.42%', branches: '0%', functions: '0%', lines: '4.67%' }, - swiper: { statements: '3.77%', branches: '0.9%', functions: '1.4%', lines: '3.89%' }, + swiper: { statements: '57.55%', branches: '37.1%', functions: '67.6%', lines: '59.74%' }, switch: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, tabBar: { statements: '10%', branches: '0%', functions: '0%', lines: '10.81%' }, table: { statements: '100%', branches: '90%', functions: '100%', lines: '100%' }, diff --git a/src/action-sheet/__tests__/action-sheet.test.tsx b/src/action-sheet/__tests__/action-sheet.test.tsx new file mode 100644 index 000000000..1c00ed2ed --- /dev/null +++ b/src/action-sheet/__tests__/action-sheet.test.tsx @@ -0,0 +1,409 @@ +import React from 'react'; +import { describe, expect, it, act, beforeEach, afterEach, render } from '@test/utils'; +import { vi } from 'vitest'; +import { AppIcon } from 'tdesign-icons-react'; +import ActionSheet from '../index'; +import type { ActionSheetItem } from '../type'; + +describe('ActionSheet', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + ActionSheet.close(); + // ActionSheet.close()已经负责清理DOM,不需要额外的innerHTML清空 + }); + + describe('props', () => { + it(': visible & items', async () => { + const items = ['选项1', '选项2', '选项3']; + expect(document.querySelector('.t-action-sheet')).toBeFalsy(); + + await act(async () => { + ActionSheet.show({ + items, + visible: true, + }); + }); + + expect(document.querySelector('.t-action-sheet')).toBeTruthy(); + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems.length).toBe(items.length); + menuItems.forEach((item, index) => { + expect(item.textContent?.trim()).toBe(items[index]); + }); + }); + + it(': theme', async () => { + const testTheme = async (theme, target) => { + await act(async () => { + ActionSheet.show({ + items: ['选项1', '选项2'], + theme, + }); + }); + expect(document.querySelector(target)).toBeTruthy(); + }; + await testTheme('list', '.t-action-sheet__list'); + await testTheme('grid', '.t-action-sheet__grid'); + }); + + it(': align', async () => { + const testAlign = async (align) => { + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + align, + }); + }); + expect(document.querySelector(`.t-action-sheet__list-item--${align}`)).toBeTruthy(); + }; + await testAlign('left'); + }); + + it(': showCancel & cancelText', async () => { + const cancelText = '关闭'; + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + showCancel: true, + cancelText, + }); + }); + const cancelBtn = document.querySelector('.t-action-sheet__cancel'); + expect(cancelBtn).toBeTruthy(); + expect(cancelBtn?.textContent?.trim()).toBe(cancelText); + + ActionSheet.close(); + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + showCancel: false, + }); + }); + expect(document.querySelector('.t-action-sheet__cancel')).toBeFalsy(); + }); + + it(': description', async () => { + const description = '这是一个描述'; + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + description, + }); + }); + const descEl = document.querySelector('.t-action-sheet__description'); + expect(descEl).toBeTruthy(); + expect(descEl?.textContent?.trim()).toBe(description); + }); + + it(': showOverlay', async () => { + const testShowOverlay = async (showOverlay) => { + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + showOverlay, + }); + }); + const overlay = document.querySelector('.t-overlay'); + if (showOverlay) { + expect(overlay).toBeTruthy(); + } else { + expect(overlay).toBeFalsy(); + } + }; + await testShowOverlay(true); + ActionSheet.close(); + await testShowOverlay(false); + }); + + it(': items with ActionSheetItem', async () => { + const items: ActionSheetItem[] = [ + { label: '选项1', color: '#ff0000' }, + { label: '选项2', disabled: true }, + { label: '选项3', icon: }, + ]; + await act(async () => { + ActionSheet.show({ + items, + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems.length).toBe(items.length); + expect(menuItems[0].querySelector('.t-action-sheet__list-item-text')?.textContent?.trim()).toBe('选项1'); + expect(menuItems[0].getAttribute('style')).toContain('color: rgb(255, 0, 0)'); + expect(menuItems[1].getAttribute('disabled')).toBe(''); + expect(menuItems[2].querySelector('.t-icon-app')).toBeTruthy(); + }); + + it(': ActionSheetList - empty items', async () => { + // 测试空数组情况 + await act(async () => { + ActionSheet.show({ + items: [], + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems.length).toBe(0); + + // 测试undefined情况 + ActionSheet.close(); + await act(async () => { + ActionSheet.show({ + // 不提供items参数 + }); + }); + + const menuItems2 = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems2.length).toBe(0); + }); + + it(': ActionSheetList - items with badge property', async () => { + const items: ActionSheetItem[] = [ + { label: '选项1' }, + { label: '选项2', badge: { dot: true } }, + { label: '选项3', badge: { count: 10 } }, + ]; + await act(async () => { + ActionSheet.show({ + items, + theme: 'list', // 明确指定为list主题 + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems.length).toBe(items.length); + + // 验证没有badge的item + expect(menuItems[0].querySelector('.t-badge')).toBeFalsy(); + expect(menuItems[0].querySelector('.t-action-sheet__list-item-text')?.textContent?.trim()).toBe('选项1'); + + // 验证带dot类型badge的item + expect(menuItems[1].querySelector('.t-badge')).toBeTruthy(); + expect(menuItems[1].querySelector('.t-badge--dot')).toBeTruthy(); + + // 验证带count类型badge的item + expect(menuItems[2].querySelector('.t-badge')).toBeTruthy(); + expect(menuItems[2].querySelector('.t-badge--basic')?.textContent?.trim()).toBe('10'); + }); + + it(': count (grid theme)', async () => { + const count = 4; + const items = Array(8).fill('选项'); + await act(async () => { + ActionSheet.show({ + items, + theme: 'grid', + count, + }); + }); + + // ActionSheetGrid使用Grid组件,所以查找GridItem生成的元素 + const gridItems = document.querySelectorAll('.t-grid-item'); + expect(gridItems.length).toBe(items.length); + + // 验证是否正确渲染了网格容器 + expect(document.querySelector('.t-action-sheet__grid')).toBeTruthy(); + }); + }); + + describe('event', () => { + it(': onSelected', async () => { + const onSelected = vi.fn(); + const items = ['选项1', '选项2']; + await act(async () => { + ActionSheet.show({ + items, + onSelected, + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + await act(async () => { + menuItems[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + expect(onSelected).toHaveBeenCalledWith(items[0], 0); + }); + + it(': onCancel', async () => { + const onCancel = vi.fn(); + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + onCancel, + }); + }); + + const cancelBtn = document.querySelector('.t-action-sheet__cancel'); + await act(async () => { + cancelBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + expect(onCancel).toHaveBeenCalled(); + }); + + it(': onClose', async () => { + const onClose = vi.fn(); + + const { unmount } = render(); + + // 等待动画完成 + await act(async () => { + vi.advanceTimersByTime(300); + }); + + // 模拟点击遮罩层来触发onClose事件 + // 找到overlay元素并触发点击事件 + const overlay = document.querySelector('.t-overlay'); + if (overlay) { + await act(async () => { + overlay.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + // 等待动画完成 + await act(async () => { + vi.advanceTimersByTime(300); + }); + } + + // 验证onClose被调用 + expect(onClose).toHaveBeenCalledTimes(1); + + // 手动卸载组件以避免清理问题 + unmount(); + }); + }); + + describe('method', () => { + it(': show & close', async () => { + await act(async () => { + ActionSheet.show({ + items: ['选项1', '选项2'], + }); + }); + expect(document.querySelector('.t-action-sheet')).toBeTruthy(); + + await act(async () => { + ActionSheet.close(); + }); + expect(document.querySelector('.t-action-sheet')).toBeFalsy(); + }); + + it(': ActionSheetGrid - default items and count', async () => { + await act(async () => { + ActionSheet.show({ + theme: 'grid', + // 不提供items和count,测试默认值行为 + }); + }); + + const gridElement = document.querySelector('.t-action-sheet__grid'); + expect(gridElement).toBeTruthy(); + + // 验证没有items时不会渲染任何GridItem + const gridItems = document.querySelectorAll('.t-grid-item'); + expect(gridItems.length).toBe(0); + }); + + it(': ActionSheetGrid - items as ActionSheetItem objects', async () => { + const items: ActionSheetItem[] = [ + { label: '选项1', icon: }, + { label: '选项2', badge: { dot: true } }, + { label: '选项3', icon: , badge: { count: 5 } }, + ]; + + await act(async () => { + ActionSheet.show({ + items, + theme: 'grid', + count: 4, + }); + }); + + const gridItems = document.querySelectorAll('.t-grid-item'); + expect(gridItems.length).toBe(items.length); + + // 验证每个item的属性是否正确渲染 + expect(gridItems[0].querySelector('.t-icon-app')).toBeTruthy(); + expect(gridItems[0].querySelector('.t-grid-item__title')?.textContent?.trim()).toBe('选项1'); + + // 验证badge是否正确渲染 + expect(gridItems[1].querySelector('.t-badge')).toBeTruthy(); + expect(gridItems[2].querySelector('.t-badge')?.textContent?.trim()).toBe('5'); + }); + + it(': ActionSheetGrid - onSelected method with correct index calculation', async () => { + const onSelected = vi.fn(); + const items = Array(12).fill('选项'); // 创建12个选项,count=4时应该有3页,每页4个选项 + + await act(async () => { + ActionSheet.show({ + items, + theme: 'grid', + count: 4, + onSelected, + }); + }); + + // 模拟点击第2页的第2个选项(索引应该是 4 + 1 = 5) + const swiperItem = document.querySelector('.t-swiper-item'); + const gridItems = swiperItem?.querySelectorAll('.t-grid-item'); + + if (gridItems && gridItems.length > 0) { + await act(async () => { + gridItems[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + // 验证onSelected被调用且索引正确 + expect(onSelected).toHaveBeenCalledTimes(1); + expect(onSelected).toHaveBeenCalledWith('选项', 0); // 第一页第一个选项的索引是0 + } + }); + + it(': show with multiple calls', async () => { + // 连续调用show,应该只显示最后一个 + await act(async () => { + ActionSheet.show({ + items: ['选项1'], + }); + ActionSheet.show({ + items: ['选项2', '选项3'], + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems.length).toBe(2); + expect(menuItems[0].textContent?.trim()).toBe('选项2'); + }); + + it(': disabled item should not trigger onSelected', async () => { + const onSelected = vi.fn(); + const items: ActionSheetItem[] = [{ label: '正常选项' }, { label: '禁用选项', disabled: true }]; + await act(async () => { + ActionSheet.show({ + items, + onSelected, + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + await act(async () => { + menuItems[1].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + expect(onSelected).not.toHaveBeenCalled(); + + // 确认正常选项可以触发 + await act(async () => { + menuItems[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + expect(onSelected).toHaveBeenCalledWith(items[0], 0); + }); + }); +}); From fd70badbf6ef195bb883dbe610c6d327218edba9 Mon Sep 17 00:00:00 2001 From: Dubhe Date: Thu, 18 Sep 2025 21:37:54 +0800 Subject: [PATCH 2/2] test(action-sheet): add unit test --- .../__tests__/action-sheet.test.tsx | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/action-sheet/__tests__/action-sheet.test.tsx b/src/action-sheet/__tests__/action-sheet.test.tsx index 1c00ed2ed..fcd466af2 100644 --- a/src/action-sheet/__tests__/action-sheet.test.tsx +++ b/src/action-sheet/__tests__/action-sheet.test.tsx @@ -13,7 +13,6 @@ describe('ActionSheet', () => { afterEach(() => { vi.useRealTimers(); ActionSheet.close(); - // ActionSheet.close()已经负责清理DOM,不需要额外的innerHTML清空 }); describe('props', () => { @@ -140,7 +139,6 @@ describe('ActionSheet', () => { }); it(': ActionSheetList - empty items', async () => { - // 测试空数组情况 await act(async () => { ActionSheet.show({ items: [], @@ -150,7 +148,6 @@ describe('ActionSheet', () => { const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); expect(menuItems.length).toBe(0); - // 测试undefined情况 ActionSheet.close(); await act(async () => { ActionSheet.show({ @@ -171,22 +168,19 @@ describe('ActionSheet', () => { await act(async () => { ActionSheet.show({ items, - theme: 'list', // 明确指定为list主题 + theme: 'list', }); }); const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); expect(menuItems.length).toBe(items.length); - // 验证没有badge的item expect(menuItems[0].querySelector('.t-badge')).toBeFalsy(); expect(menuItems[0].querySelector('.t-action-sheet__list-item-text')?.textContent?.trim()).toBe('选项1'); - // 验证带dot类型badge的item expect(menuItems[1].querySelector('.t-badge')).toBeTruthy(); expect(menuItems[1].querySelector('.t-badge--dot')).toBeTruthy(); - // 验证带count类型badge的item expect(menuItems[2].querySelector('.t-badge')).toBeTruthy(); expect(menuItems[2].querySelector('.t-badge--basic')?.textContent?.trim()).toBe('10'); }); @@ -202,11 +196,9 @@ describe('ActionSheet', () => { }); }); - // ActionSheetGrid使用Grid组件,所以查找GridItem生成的元素 const gridItems = document.querySelectorAll('.t-grid-item'); expect(gridItems.length).toBe(items.length); - // 验证是否正确渲染了网格容器 expect(document.querySelector('.t-action-sheet__grid')).toBeTruthy(); }); }); @@ -252,29 +244,23 @@ describe('ActionSheet', () => { const { unmount } = render(); - // 等待动画完成 await act(async () => { vi.advanceTimersByTime(300); }); - // 模拟点击遮罩层来触发onClose事件 - // 找到overlay元素并触发点击事件 const overlay = document.querySelector('.t-overlay'); if (overlay) { await act(async () => { overlay.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); - // 等待动画完成 await act(async () => { vi.advanceTimersByTime(300); }); } - // 验证onClose被调用 expect(onClose).toHaveBeenCalledTimes(1); - // 手动卸载组件以避免清理问题 unmount(); }); }); @@ -305,7 +291,6 @@ describe('ActionSheet', () => { const gridElement = document.querySelector('.t-action-sheet__grid'); expect(gridElement).toBeTruthy(); - // 验证没有items时不会渲染任何GridItem const gridItems = document.querySelectorAll('.t-grid-item'); expect(gridItems.length).toBe(0); }); @@ -328,18 +313,16 @@ describe('ActionSheet', () => { const gridItems = document.querySelectorAll('.t-grid-item'); expect(gridItems.length).toBe(items.length); - // 验证每个item的属性是否正确渲染 expect(gridItems[0].querySelector('.t-icon-app')).toBeTruthy(); expect(gridItems[0].querySelector('.t-grid-item__title')?.textContent?.trim()).toBe('选项1'); - // 验证badge是否正确渲染 expect(gridItems[1].querySelector('.t-badge')).toBeTruthy(); expect(gridItems[2].querySelector('.t-badge')?.textContent?.trim()).toBe('5'); }); it(': ActionSheetGrid - onSelected method with correct index calculation', async () => { const onSelected = vi.fn(); - const items = Array(12).fill('选项'); // 创建12个选项,count=4时应该有3页,每页4个选项 + const items = Array(12).fill('选项'); await act(async () => { ActionSheet.show({ @@ -350,7 +333,6 @@ describe('ActionSheet', () => { }); }); - // 模拟点击第2页的第2个选项(索引应该是 4 + 1 = 5) const swiperItem = document.querySelector('.t-swiper-item'); const gridItems = swiperItem?.querySelectorAll('.t-grid-item'); @@ -359,14 +341,12 @@ describe('ActionSheet', () => { gridItems[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); }); - // 验证onSelected被调用且索引正确 expect(onSelected).toHaveBeenCalledTimes(1); - expect(onSelected).toHaveBeenCalledWith('选项', 0); // 第一页第一个选项的索引是0 + expect(onSelected).toHaveBeenCalledWith('选项', 0); } }); it(': show with multiple calls', async () => { - // 连续调用show,应该只显示最后一个 await act(async () => { ActionSheet.show({ items: ['选项1'], @@ -398,7 +378,6 @@ describe('ActionSheet', () => { expect(onSelected).not.toHaveBeenCalled(); - // 确认正常选项可以触发 await act(async () => { menuItems[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); });