a5d86a92c2
feat: 升级项目 Logo、完善 toast 提示及优化工程化配置 ### Features - **logo**: 替换全套项目图标,更新 wxt.config.ts 浏览器扩展图标配置。 ### Bug Fixes - **ui/toast**: 引入 <Toaster> 组件修复 toast 无法显示问题,并调整位置为正下方。 - **i18n**: 统一将 t() 函数中的 `.` 和 `:` 转换为 `_`,修复国际化文案读取失败问题。 - **context-menu**: 限制 srcUrl 检查仅在二维码图片菜单项生效,避免误判。 - **hooks**: 修复 pre-push 钩子,仅对变更文件进行 eslint 检查,并支持无 upstream 时的回退对比。 - **components**: 修复 CopyButton 动画与组件导入方式,并补齐无障碍属性与测试。 - **async**: 延迟生成二维码以正确渲染 loading 状态,并适配相关异步测试。 ### Refactor & Cleans - **clean**: 清理未使用的组件(ToolCard)、Hook(useDebounce)及多个未使用的工具函数导出。 - **structure**: 统一移动测试文件至 __tests__ 目录,移除不必要的类型导出。 ### Documentation - **docs**: 在 AGENTS.md 中明确规范 Git Commit 必须使用中文描述。
214 lines
6.3 KiB
TypeScript
214 lines
6.3 KiB
TypeScript
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
import { renderHook, act } from '@testing-library/react';
|
|
import { useContextMenuData, saveContextMenuData } from '@/utils/useContextMenuData';
|
|
|
|
describe('useContextMenuData', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('saveContextMenuData', () => {
|
|
it('应该保存数据到 storage 并添加时间戳', async () => {
|
|
const data = { featureKey: 'jwt' as const, payload: 'test-token' };
|
|
vi.setSystemTime(new Date('2024-01-01T12:00:00Z'));
|
|
|
|
await saveContextMenuData(data);
|
|
|
|
expect(chrome.storage.local.set).toHaveBeenCalledWith({
|
|
'contextMenu/pendingData': {
|
|
featureKey: 'jwt',
|
|
payload: 'test-token',
|
|
timestamp: 1704110400000,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('应该正确处理不同的 featureKey', async () => {
|
|
const data = { featureKey: 'timestamp' as const, payload: '1234567890' };
|
|
|
|
await saveContextMenuData(data);
|
|
|
|
expect(chrome.storage.local.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
'contextMenu/pendingData': expect.objectContaining({
|
|
featureKey: 'timestamp',
|
|
payload: '1234567890',
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('useContextMenuData Hook', () => {
|
|
it('当 storage 中有匹配数据时应调用 onData 回调', async () => {
|
|
const mockData = {
|
|
featureKey: 'jwt',
|
|
payload: 'test-token',
|
|
timestamp: Date.now(),
|
|
};
|
|
(chrome.storage.local.get as any).mockResolvedValue({
|
|
'contextMenu/pendingData': mockData,
|
|
});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onData).toHaveBeenCalledWith('test-token');
|
|
});
|
|
});
|
|
|
|
it('当 storage 中没有数据时不应调用 onData 回调', async () => {
|
|
(chrome.storage.local.get as any).mockResolvedValue({});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onData).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('当 featureKey 不匹配时不应调用 onData 回调', async () => {
|
|
const mockData = {
|
|
featureKey: 'timestamp',
|
|
payload: '1234567890',
|
|
timestamp: Date.now(),
|
|
};
|
|
(chrome.storage.local.get as any).mockResolvedValue({
|
|
'contextMenu/pendingData': mockData,
|
|
});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onData).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('当数据过期时不应调用 onData 回调并删除数据', async () => {
|
|
const now = Date.now();
|
|
vi.setSystemTime(now);
|
|
|
|
const mockData = {
|
|
featureKey: 'jwt',
|
|
payload: 'test-token',
|
|
timestamp: now - 6000,
|
|
};
|
|
(chrome.storage.local.get as any).mockResolvedValue({
|
|
'contextMenu/pendingData': mockData,
|
|
});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onData).not.toHaveBeenCalled();
|
|
expect(chrome.storage.local.remove).toHaveBeenCalledWith(['contextMenu/pendingData']);
|
|
});
|
|
});
|
|
|
|
it('消费数据后应删除 storage 中的数据', async () => {
|
|
const mockData = {
|
|
featureKey: 'jwt',
|
|
payload: 'test-token',
|
|
timestamp: Date.now(),
|
|
};
|
|
(chrome.storage.local.get as any).mockResolvedValue({
|
|
'contextMenu/pendingData': mockData,
|
|
});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(chrome.storage.local.remove).toHaveBeenCalledWith(['contextMenu/pendingData']);
|
|
});
|
|
});
|
|
|
|
it('当 storage 变化且 featureKey 匹配时应调用 onData 回调', async () => {
|
|
(chrome.storage.local.get as any).mockResolvedValue({});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
const storageChangeHandler = (chrome.storage.onChanged.addListener as any).mock.calls[0][0];
|
|
|
|
const mockData = {
|
|
featureKey: 'jwt',
|
|
payload: 'new-token',
|
|
timestamp: Date.now(),
|
|
};
|
|
(chrome.storage.local.get as any).mockResolvedValue({
|
|
'contextMenu/pendingData': mockData,
|
|
});
|
|
|
|
await act(async () => {
|
|
storageChangeHandler({
|
|
'contextMenu/pendingData': { newValue: mockData },
|
|
});
|
|
});
|
|
|
|
await vi.waitFor(() => {
|
|
expect(onData).toHaveBeenCalledWith('new-token');
|
|
});
|
|
});
|
|
|
|
it('当 storage 变化但 featureKey 不匹配时不应调用 onData 回调', async () => {
|
|
(chrome.storage.local.get as any).mockResolvedValue({});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
const storageChangeHandler = (chrome.storage.onChanged.addListener as any).mock.calls[0][0];
|
|
|
|
const mockData = {
|
|
featureKey: 'timestamp',
|
|
payload: '1234567890',
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
await act(async () => {
|
|
storageChangeHandler({
|
|
'contextMenu/pendingData': { newValue: mockData },
|
|
});
|
|
});
|
|
|
|
expect(onData).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('当 storage 变化但数据被删除时不应调用 onData 回调', async () => {
|
|
(chrome.storage.local.get as any).mockResolvedValue({});
|
|
|
|
const onData = vi.fn();
|
|
renderHook(() => useContextMenuData({ featureKey: 'jwt', onData }));
|
|
|
|
const storageChangeHandler = (chrome.storage.onChanged.addListener as any).mock.calls[0][0];
|
|
|
|
await act(async () => {
|
|
storageChangeHandler({
|
|
'contextMenu/pendingData': { newValue: null },
|
|
});
|
|
});
|
|
|
|
expect(onData).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('组件卸载时应移除 storage 变化监听器', () => {
|
|
const { unmount } = renderHook(() =>
|
|
useContextMenuData({ featureKey: 'jwt', onData: vi.fn() }),
|
|
);
|
|
|
|
unmount();
|
|
|
|
expect(chrome.storage.onChanged.removeListener).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|