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..fcd466af2 --- /dev/null +++ b/src/action-sheet/__tests__/action-sheet.test.tsx @@ -0,0 +1,388 @@ +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(); + }); + + 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); + + 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', + }); + }); + + const menuItems = document.querySelectorAll('.t-action-sheet__list-item'); + expect(menuItems.length).toBe(items.length); + + expect(menuItems[0].querySelector('.t-badge')).toBeFalsy(); + expect(menuItems[0].querySelector('.t-action-sheet__list-item-text')?.textContent?.trim()).toBe('选项1'); + + expect(menuItems[1].querySelector('.t-badge')).toBeTruthy(); + expect(menuItems[1].querySelector('.t-badge--dot')).toBeTruthy(); + + 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, + }); + }); + + 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); + }); + + const overlay = document.querySelector('.t-overlay'); + if (overlay) { + await act(async () => { + overlay.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + await act(async () => { + vi.advanceTimersByTime(300); + }); + } + + 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(); + + 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); + + expect(gridItems[0].querySelector('.t-icon-app')).toBeTruthy(); + expect(gridItems[0].querySelector('.t-grid-item__title')?.textContent?.trim()).toBe('选项1'); + + 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('选项'); + + await act(async () => { + ActionSheet.show({ + items, + theme: 'grid', + count: 4, + onSelected, + }); + }); + + 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 })); + }); + + expect(onSelected).toHaveBeenCalledTimes(1); + expect(onSelected).toHaveBeenCalledWith('选项', 0); + } + }); + + it(': show with multiple calls', async () => { + 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); + }); + }); +});