3fb99ea49f
- 新增 utils/contextMenu.ts 封装菜单配置和解析函数 - 在 background.ts 监听 onInstalled 初始化菜单 - 实现 contextMenus.onClicked 点击事件处理 - 分流逻辑:优先发送到侧边栏,否则打开 options 页面 - 添加 contextMenu 单元测试(15个测试用例) - 更新 vitest.setup.ts 添加 contextMenus mock
127 lines
3.2 KiB
TypeScript
127 lines
3.2 KiB
TypeScript
import '@testing-library/jest-dom';
|
||
import { vi, beforeEach, afterEach } from 'vitest';
|
||
|
||
// Mock react-i18next
|
||
vi.mock('react-i18next', () => ({
|
||
useTranslation: () => ({
|
||
t: (key: string) => key,
|
||
i18n: {
|
||
changeLanguage: vi.fn(),
|
||
language: 'zh-CN',
|
||
},
|
||
}),
|
||
initReactI18next: {
|
||
type: '3rdParty',
|
||
init: vi.fn(),
|
||
},
|
||
}));
|
||
|
||
const storageMock = {
|
||
local: {
|
||
get: vi.fn().mockImplementation(() => Promise.resolve({})),
|
||
set: vi.fn().mockImplementation(() => Promise.resolve()),
|
||
remove: vi.fn().mockImplementation(() => Promise.resolve()),
|
||
clear: vi.fn().mockImplementation(() => Promise.resolve()),
|
||
},
|
||
session: {
|
||
get: vi.fn().mockImplementation(() => Promise.resolve({})),
|
||
set: vi.fn().mockImplementation(() => Promise.resolve()),
|
||
remove: vi.fn().mockImplementation(() => Promise.resolve()),
|
||
clear: vi.fn().mockImplementation(() => Promise.resolve()),
|
||
},
|
||
onChanged: {
|
||
addListener: vi.fn(),
|
||
removeListener: vi.fn(),
|
||
hasListener: vi.fn(),
|
||
},
|
||
};
|
||
|
||
Object.defineProperty(global, 'chrome', {
|
||
value: {
|
||
storage: storageMock,
|
||
tabs: {
|
||
query: vi.fn().mockResolvedValue([]),
|
||
get: vi.fn(),
|
||
sendMessage: vi.fn().mockResolvedValue(undefined),
|
||
create: vi.fn().mockResolvedValue({}),
|
||
},
|
||
runtime: {
|
||
id: 'test-extension-id',
|
||
getURL: vi.fn((path: string) => `chrome-extension://test-extension-id/${path}`),
|
||
openOptionsPage: vi.fn(),
|
||
onInstalled: {
|
||
addListener: vi.fn(),
|
||
removeListener: vi.fn(),
|
||
},
|
||
},
|
||
action: {
|
||
onClicked: {
|
||
addListener: vi.fn(),
|
||
removeListener: vi.fn(),
|
||
},
|
||
},
|
||
sidePanel: {
|
||
open: vi.fn().mockResolvedValue(undefined),
|
||
},
|
||
scripting: {
|
||
executeScript: vi.fn().mockResolvedValue([{ result: undefined }]),
|
||
},
|
||
cookies: {
|
||
getAll: vi.fn().mockResolvedValue([]),
|
||
remove: vi.fn().mockResolvedValue(undefined),
|
||
},
|
||
contextMenus: {
|
||
create: vi.fn(),
|
||
onClicked: {
|
||
addListener: vi.fn(),
|
||
removeListener: vi.fn(),
|
||
},
|
||
ContextType: {
|
||
ALL: 'all',
|
||
PAGE: 'page',
|
||
FRAME: 'frame',
|
||
SELECTION: 'selection',
|
||
LINK: 'link',
|
||
EDITABLE: 'editable',
|
||
IMAGE: 'image',
|
||
VIDEO: 'video',
|
||
AUDIO: 'audio',
|
||
LAUNCHER: 'launcher',
|
||
BROWSER_ACTION: 'browser_action',
|
||
PAGE_ACTION: 'page_action',
|
||
ACTION: 'action',
|
||
},
|
||
},
|
||
},
|
||
writable: true,
|
||
});
|
||
|
||
beforeEach(() => {
|
||
vi.clearAllMocks();
|
||
// Ensure storage mocks return objects even after clearAllMocks
|
||
storageMock.local.get.mockResolvedValue({});
|
||
storageMock.session.get.mockResolvedValue({});
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
// 全局 matchMedia mock(ThemeModeProvider 依赖)
|
||
// 必须在 beforeEach 中设置,因为 afterEach 的 restoreAllMocks 会清除它
|
||
beforeEach(() => {
|
||
Object.defineProperty(window, 'matchMedia', {
|
||
writable: true,
|
||
value: vi.fn().mockImplementation((query: string) => ({
|
||
matches: false,
|
||
media: query,
|
||
onchange: null,
|
||
addListener: vi.fn(),
|
||
removeListener: vi.fn(),
|
||
addEventListener: vi.fn(),
|
||
removeEventListener: vi.fn(),
|
||
dispatchEvent: vi.fn(),
|
||
})),
|
||
});
|
||
});
|