From 9990d862706f3fc4bd4f1c11944242eddeb42329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Sat, 6 Jun 2026 00:49:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=E9=AB=98?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7=E6=AD=BB=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除未使用的代码以提高代码质量和可维护性: 删除的文件: - src/pages/Dashboard/ToolCard.tsx:未被使用的组件 - src/utils/useDebounce.ts:未被使用的 hook - src/components/__tests__/ToolCard.test.tsx:对应组件的测试文件 删除的导出函数: - chromeTabs: getActiveTab, getActiveTabDomain, ensureContentScriptInjected - clipboard: copyImageToClipboard - storageCleaner: isEmptyResult - chromeI18n: preloadNamespaces - useContextMenuData: clearContextMenuData 更新了对应的测试文件以保持一致性 总计删除 525 行代码,所有 473 个测试通过 --- src/components/__tests__/ToolCard.test.tsx | 118 --------------- src/pages/Dashboard/ToolCard.tsx | 107 -------------- src/utils/__tests__/chromeTabs.test.ts | 136 +----------------- src/utils/__tests__/clipboard.test.ts | 36 +---- .../__tests__/useContextMenuData.test.ts | 14 +- src/utils/chromeI18n.ts | 8 -- src/utils/chromeTabs.ts | 54 ------- src/utils/clipboard.ts | 18 --- src/utils/storageCleaner.ts | 7 - src/utils/useContextMenuData.ts | 7 - src/utils/useDebounce.ts | 24 ---- 11 files changed, 4 insertions(+), 525 deletions(-) delete mode 100644 src/components/__tests__/ToolCard.test.tsx delete mode 100644 src/pages/Dashboard/ToolCard.tsx delete mode 100644 src/utils/useDebounce.ts diff --git a/src/components/__tests__/ToolCard.test.tsx b/src/components/__tests__/ToolCard.test.tsx deleted file mode 100644 index bf3da2e..0000000 --- a/src/components/__tests__/ToolCard.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { act, fireEvent, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import ToolCard from '@/pages/Dashboard/ToolCard'; -import { Clock } from 'lucide-react'; - -describe('ToolCard 组件', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe('渲染测试', () => { - it('应渲染标题和描述', () => { - render( - {}} - />, - ); - - expect(screen.getByText('测试工具')).toBeInTheDocument(); - expect(screen.getByText('这是一个测试工具')).toBeInTheDocument(); - }); - - it('无描述时仅渲染标题', () => { - render( {}} />); - - expect(screen.getByText('仅标题')).toBeInTheDocument(); - }); - - it('应渲染图标', () => { - const { container } = render( - {}} />, - ); - - const svgElement = container.querySelector('svg'); - expect(svgElement).toBeInTheDocument(); - }); - - it('提供快照内容时应渲染快照', () => { - render( - {}} - snapshot={
快照内容
} - />, - ); - - expect(screen.getByTestId('snapshot')).toBeInTheDocument(); - }); - - it('未提供快照时不渲染快照区域', () => { - const { container } = render( - {}} - onNavigate={function (): void { - throw new Error('Function not implemented.'); - }} - />, - ); - - expect(container.querySelector('[data-testid="snapshot"]')).not.toBeInTheDocument(); - }); - - it('应使用 CardActionArea 渲染,支持键盘聚焦', () => { - render( {}} />); - - const button = screen.getByRole('button', { name: /可聚焦/ }); - expect(button).toBeInTheDocument(); - }); - }); - - describe('交互测试', () => { - it('点击时应调用 onClick', () => { - const handleClick = vi.fn(); - render(); - - const button = screen.getByRole('button', { name: /可点击/ }); - fireEvent.click(button); - - expect(handleClick).toHaveBeenCalledTimes(1); - }); - - it('按 Enter 键时应调用 onClick', async () => { - const handleClick = vi.fn(); - render( - , - ); - - const button = screen.getByRole('button', { name: /键盘可触发/ }); - await act(async () => { - button.focus(); - await userEvent.keyboard('{Enter}'); - }); - - expect(handleClick).toHaveBeenCalledTimes(1); - }); - }); - - describe('样式测试', () => { - it('应应用自定义颜色代码', () => { - const { container } = render( - {}} />, - ); - - const svgElement = container.querySelector('svg'); - expect(svgElement).toBeInTheDocument(); - }); - }); -}); diff --git a/src/pages/Dashboard/ToolCard.tsx b/src/pages/Dashboard/ToolCard.tsx deleted file mode 100644 index e8f6379..0000000 --- a/src/pages/Dashboard/ToolCard.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import type { ComponentType } from 'react'; -import React from 'react'; -import type { LucideProps } from 'lucide-react'; -import { ChevronRight } from 'lucide-react'; -import type { PaletteColorKey } from '@/config/features'; -import { cn } from '@/lib/utils'; - -const PALETTE_COLORS: Record = { - primary: '13, 148, 136', // teal - success: '22, 163, 74', // green - warning: '217, 119, 6', // amber (存储清理的橙色轴) - error: '220, 38, 38', // red - secondary: '147, 51, 232', - info: '37, 99, 235', // blue -}; - -export interface ToolCardProps extends React.HTMLAttributes { - title: string; - description?: string; - snapshot?: React.ReactNode; - colorKey: PaletteColorKey; - icon: ComponentType; - onNavigate: () => void; -} - -export default function ToolCard({ - title, - description, - snapshot, - colorKey, - icon: IconComponent, - onNavigate, - className, - ...props -}: ToolCardProps) { - const rgbValues = PALETTE_COLORS[colorKey]; - - return ( -
- {/* 上半部分:核心信息交互排版轴 */} -
-
- {/* 左侧圆形图标容器 */} -
- -
- - {/* 中间文字描述区:利用 flex-1 min-w-0 防御文本过长发生恶性撑开 */} -
-

- {title} -

- {description && ( -

- {description} -

- )} -
-
- - {/* 右侧指示小箭头 */} -
- -
- - {/* 覆盖整个上半部分的绝对定位隐形跳转层(A11y 无障碍标准合规) */} -
- - {/* 下半部分:未来的动态预览沙箱独立承载区 */} - {snapshot != null && ( -
- {snapshot} -
- )} -
- ); -} - -ToolCard.displayName = 'ToolCard'; diff --git a/src/utils/__tests__/chromeTabs.test.ts b/src/utils/__tests__/chromeTabs.test.ts index c992863..b9aabd6 100644 --- a/src/utils/__tests__/chromeTabs.test.ts +++ b/src/utils/__tests__/chromeTabs.test.ts @@ -1,101 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; -import { - getActiveTab, - getActiveTabDomain, - openExtensionPage, - ensureContentScriptInjected, -} from '@/utils/chromeTabs'; +import { openExtensionPage } from '@/utils/chromeTabs'; describe('chromeTabs', () => { - describe('getActiveTab', () => { - it('应该返回当前活动标签页', async () => { - const mockTab = { id: 1, url: 'https://example.com', title: 'Example' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await getActiveTab(); - - expect(result).toEqual(mockTab); - expect(chrome.tabs.query).toHaveBeenCalledWith({ active: true, currentWindow: true }); - }); - - it('当没有活动标签页时应返回 null', async () => { - (chrome.tabs.query as any).mockResolvedValue([]); - - const result = await getActiveTab(); - - expect(result).toBeNull(); - }); - - it('当查询失败时应返回 null 并记录错误', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - (chrome.tabs.query as any).mockRejectedValue(new Error('Permission denied')); - - const result = await getActiveTab(); - - expect(result).toBeNull(); - expect(consoleSpy).toHaveBeenCalledWith('获取活动标签页失败:', expect.any(Error)); - consoleSpy.mockRestore(); - }); - }); - - describe('getActiveTabDomain', () => { - it('应该返回当前活动标签页的域名', async () => { - const mockTab = { id: 1, url: 'https://example.com/path?query=1' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await getActiveTabDomain(); - - expect(result).toBe('example.com'); - }); - - it('应该处理带有端口的 URL', async () => { - const mockTab = { id: 1, url: 'https://example.com:8080/path' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await getActiveTabDomain(); - - expect(result).toBe('example.com'); - }); - - it('当标签页没有 URL 时应返回空字符串', async () => { - const mockTab = { id: 1 } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await getActiveTabDomain(); - - expect(result).toBe(''); - }); - - it('当没有活动标签页时应返回空字符串', async () => { - (chrome.tabs.query as any).mockResolvedValue([]); - - const result = await getActiveTabDomain(); - - expect(result).toBe(''); - }); - - it('当 URL 解析失败时应返回空字符串并记录错误', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - const mockTab = { id: 1, url: 'not-a-valid-url' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await getActiveTabDomain(); - - expect(result).toBe(''); - expect(consoleSpy).toHaveBeenCalledWith('解析域名失败:', expect.any(Error)); - consoleSpy.mockRestore(); - }); - - it('应该处理 chrome-extension URL', async () => { - const mockTab = { id: 1, url: 'chrome-extension://abc123/popup.html' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await getActiveTabDomain(); - - expect(result).toBe('abc123'); - }); - }); - describe('openExtensionPage', () => { it('应该在新标签页中打开扩展页面', async () => { await openExtensionPage('popup.html'); @@ -116,44 +22,4 @@ describe('chromeTabs', () => { consoleSpy.mockRestore(); }); }); - - describe('ensureContentScriptInjected', () => { - it('当存在活动标签页时应返回 true', async () => { - const mockTab = { id: 123, url: 'https://example.com' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await ensureContentScriptInjected(); - - expect(result).toBe(true); - }); - - it('当没有活动标签页时应返回 false', async () => { - (chrome.tabs.query as any).mockResolvedValue([]); - - const result = await ensureContentScriptInjected(); - - expect(result).toBe(false); - }); - - it('当标签页没有 id 时应返回 false', async () => { - const mockTab = { url: 'https://example.com' } as chrome.tabs.Tab; - (chrome.tabs.query as any).mockResolvedValue([mockTab]); - - const result = await ensureContentScriptInjected(); - - expect(result).toBe(false); - }); - - it('当整体操作失败时应返回 false 并记录错误', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - (chrome.tabs.query as any).mockRejectedValue(new Error('Query failed')); - - const result = await ensureContentScriptInjected(); - - expect(result).toBe(false); - // getActiveTab catches the error and logs "获取活动标签页失败" - expect(consoleSpy).toHaveBeenCalledWith('获取活动标签页失败:', expect.any(Error)); - consoleSpy.mockRestore(); - }); - }); }); diff --git a/src/utils/__tests__/clipboard.test.ts b/src/utils/__tests__/clipboard.test.ts index 568cb14..5aedb84 100644 --- a/src/utils/__tests__/clipboard.test.ts +++ b/src/utils/__tests__/clipboard.test.ts @@ -1,14 +1,5 @@ -import { describe, expect, it, vi, beforeAll } from 'vitest'; -import { copyTextToClipboard, copyImageToClipboard } from '@/utils/clipboard'; - -// Mock ClipboardItem for test environment -class MockClipboardItem { - constructor(public items: Record) {} -} - -beforeAll(() => { - (globalThis as any).ClipboardItem = MockClipboardItem; -}); +import { describe, expect, it, vi } from 'vitest'; +import { copyTextToClipboard } from '@/utils/clipboard'; describe('clipboard', () => { describe('copyTextToClipboard', () => { @@ -31,27 +22,4 @@ describe('clipboard', () => { expect(result).toBe(false); }); }); - - describe('copyImageToClipboard', () => { - it('复制成功时应返回 true', async () => { - const write = vi.fn().mockResolvedValue(undefined); - Object.assign(navigator, { clipboard: { write } }); - - const blob = new Blob(['png data'], { type: 'image/png' }); - const result = await copyImageToClipboard(blob); - - expect(result).toBe(true); - expect(write).toHaveBeenCalledTimes(1); - }); - - it('复制失败时应返回 false', async () => { - const write = vi.fn().mockRejectedValue(new Error('Permission denied')); - Object.assign(navigator, { clipboard: { write } }); - - const blob = new Blob(['png data'], { type: 'image/png' }); - const result = await copyImageToClipboard(blob); - - expect(result).toBe(false); - }); - }); }); diff --git a/src/utils/__tests__/useContextMenuData.test.ts b/src/utils/__tests__/useContextMenuData.test.ts index e0c9b97..01b2818 100644 --- a/src/utils/__tests__/useContextMenuData.test.ts +++ b/src/utils/__tests__/useContextMenuData.test.ts @@ -1,10 +1,6 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, act } from '@testing-library/react'; -import { - useContextMenuData, - saveContextMenuData, - clearContextMenuData, -} from '@/utils/useContextMenuData'; +import { useContextMenuData, saveContextMenuData } from '@/utils/useContextMenuData'; describe('useContextMenuData', () => { beforeEach(() => { @@ -48,14 +44,6 @@ describe('useContextMenuData', () => { }); }); - describe('clearContextMenuData', () => { - it('应该从 storage 中删除数据', async () => { - await clearContextMenuData(); - - expect(chrome.storage.local.remove).toHaveBeenCalledWith(['contextMenu/pendingData']); - }); - }); - describe('useContextMenuData Hook', () => { it('当 storage 中有匹配数据时应调用 onData 回调', async () => { const mockData = { diff --git a/src/utils/chromeI18n.ts b/src/utils/chromeI18n.ts index e172fd5..3442b96 100644 --- a/src/utils/chromeI18n.ts +++ b/src/utils/chromeI18n.ts @@ -66,11 +66,3 @@ export function useI18n(namespace?: string | string[]) { isLoaded: true, }; } - -/** - * 预加载命名空间(无操作,兼容 useLazyTranslation) - */ -export async function preloadNamespaces(_namespaces: string[]): Promise { - // chrome.i18n 是同步的,无需预加载 - return Promise.resolve(); -} diff --git a/src/utils/chromeTabs.ts b/src/utils/chromeTabs.ts index bf892f5..451a085 100644 --- a/src/utils/chromeTabs.ts +++ b/src/utils/chromeTabs.ts @@ -2,35 +2,6 @@ * Chrome 标签页相关工具函数 */ -/** - * 获取当前活动的标签页 - */ -export async function getActiveTab(): Promise { - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - return tab || null; - } catch (error) { - console.error('获取活动标签页失败:', error); - return null; - } -} - -/** - * 获取当前活动的标签页域名 - */ -export async function getActiveTabDomain(): Promise { - const tab = await getActiveTab(); - if (tab?.url) { - try { - const url = new URL(tab.url); - return url.hostname; - } catch (e) { - console.error('解析域名失败:', e); - } - } - return ''; -} - /** * 在新标签页中打开扩展页面 * @param page - 扩展页面路径(如 'popup.html') @@ -50,28 +21,3 @@ export async function openExtensionPage( console.error('打开扩展页面失败:', error); } } - -/** - * 确保内容脚本已注入 - */ -export async function ensureContentScriptInjected(): Promise { - try { - const tab = await getActiveTab(); - if (!tab?.id) return false; - - try { - return true; - } catch (e) { - console.log('内容脚本未注入,尝试注入...'); - console.error('注入内容脚本失败:', e); - await chrome.scripting.executeScript({ - target: { tabId: tab.id }, - files: ['/content-scripts/content.js'], - }); - return true; - } - } catch (error) { - console.error('注入内容脚本失败:', error); - return false; - } -} diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts index 0ea3a3e..d6605a5 100644 --- a/src/utils/clipboard.ts +++ b/src/utils/clipboard.ts @@ -11,21 +11,3 @@ export async function copyTextToClipboard(text: string): Promise { return false; } } - -/** - * 复制图片到剪贴板 - * @param blob 要复制的图片 - * @returns Promise 是否复制成功 - */ -export async function copyImageToClipboard(blob: Blob): Promise { - try { - await navigator.clipboard.write([ - new ClipboardItem({ - 'image/png': blob, - }), - ]); - return true; - } catch { - return false; - } -} diff --git a/src/utils/storageCleaner.ts b/src/utils/storageCleaner.ts index 1238e07..6f5ee19 100644 --- a/src/utils/storageCleaner.ts +++ b/src/utils/storageCleaner.ts @@ -381,10 +381,3 @@ export function formatCleaningResult( return t('storageCleaner:cleanedSummary', { items: parts.join(', ') }); } - -export function isEmptyResult(result: CleaningResult): boolean { - const values = Object.values(result).filter( - (r): r is StorageCleanResult => r?.success === true && r.count > 0, - ); - return values.length === 0; -} diff --git a/src/utils/useContextMenuData.ts b/src/utils/useContextMenuData.ts index 8c2b1c1..ecc7ea2 100644 --- a/src/utils/useContextMenuData.ts +++ b/src/utils/useContextMenuData.ts @@ -90,10 +90,3 @@ export async function saveContextMenuData( }; await storageUtil.set(STORAGE_KEY, pendingData); } - -/** - * 清除右键菜单待处理数据 - */ -export async function clearContextMenuData(): Promise { - await storageUtil.remove(STORAGE_KEY); -} diff --git a/src/utils/useDebounce.ts b/src/utils/useDebounce.ts deleted file mode 100644 index e9a70ac..0000000 --- a/src/utils/useDebounce.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useState, useEffect } from 'react'; - -/** - * useDebounce Hook - 防抖值 - * - * @param value - 需要防抖的值 - * @param delay - 延迟时间(毫秒) - * @returns 防抖后的值 - */ -export function useDebounce(value: T, delay: number): T { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const timer = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(timer); - }; - }, [value, delay]); - - return debouncedValue; -}