refactor: 删除高优先级死代码
删除未使用的代码以提高代码质量和可维护性: 删除的文件: - 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 个测试通过
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, Blob>) {}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -66,11 +66,3 @@ export function useI18n(namespace?: string | string[]) {
|
||||
isLoaded: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载命名空间(无操作,兼容 useLazyTranslation)
|
||||
*/
|
||||
export async function preloadNamespaces(_namespaces: string[]): Promise<void> {
|
||||
// chrome.i18n 是同步的,无需预加载
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -2,35 +2,6 @@
|
||||
* Chrome 标签页相关工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取当前活动的标签页
|
||||
*/
|
||||
export async function getActiveTab(): Promise<chrome.tabs.Tab | null> {
|
||||
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<string> {
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,3 @@ export async function copyTextToClipboard(text: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制图片到剪贴板
|
||||
* @param blob 要复制的图片
|
||||
* @returns Promise<boolean> 是否复制成功
|
||||
*/
|
||||
export async function copyImageToClipboard(blob: Blob): Promise<boolean> {
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'image/png': blob,
|
||||
}),
|
||||
]);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -90,10 +90,3 @@ export async function saveContextMenuData(
|
||||
};
|
||||
await storageUtil.set(STORAGE_KEY, pendingData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除右键菜单待处理数据
|
||||
*/
|
||||
export async function clearContextMenuData(): Promise<void> {
|
||||
await storageUtil.remove(STORAGE_KEY);
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* useDebounce Hook - 防抖值
|
||||
*
|
||||
* @param value - 需要防抖的值
|
||||
* @param delay - 延迟时间(毫秒)
|
||||
* @returns 防抖后的值
|
||||
*/
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
Reference in New Issue
Block a user