From 756ece31d3bcbd1f0288e83ad291a616dcaaeef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Fri, 22 May 2026 23:02:41 +0800 Subject: [PATCH] refactor: extend vitest setup with full chrome/browser mocks and i18n utility mock --- vitest.setup.ts | 151 ++++++++++++++++++++++++++++-------------------- 1 file changed, 87 insertions(+), 64 deletions(-) diff --git a/vitest.setup.ts b/vitest.setup.ts index 6544534..5e13962 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,18 +1,29 @@ import '@testing-library/jest-dom'; -import { vi, beforeEach, afterEach } from 'vitest'; +import { afterEach, beforeEach, vi } from 'vitest'; + +vi.mock('@/utils/useLazyTranslation', () => ({ + useLazyTranslation: (ns?: string) => ({ + // 自动承接命名空间前缀过滤,100% 模拟真实多语种直出 + t: (key: string) => (ns ? `${ns}:${key}` : key), + i18n: { + changeLanguage: vi.fn().mockResolvedValue(undefined), + language: 'zh-CN', + }, + isLoaded: true, + }), +})); -// Mock react-i18next vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, i18n: { - changeLanguage: vi.fn(), + changeLanguage: vi.fn().mockResolvedValue(undefined), language: 'zh-CN', }, }), initReactI18next: { type: '3rdParty', - init: vi.fn(), + init: vi.fn().mockResolvedValue(undefined), }, })); @@ -36,69 +47,82 @@ const storageMock = { }, }; -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', - }, +const webExtensionMock = { + storage: storageMock, + tabs: { + query: vi.fn().mockResolvedValue([]), + get: vi.fn().mockResolvedValue({}), + sendMessage: vi.fn().mockResolvedValue(undefined), + create: vi.fn().mockResolvedValue({}), + reload: vi.fn().mockResolvedValue(undefined), // ✅ 承接 MessageAction.RELOAD_TAB 刷新单元测试 + }, + 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(), }, }, - writable: true, -}); + action: { + onClicked: { + addListener: vi.fn(), + removeListener: vi.fn(), + }, + openPopup: vi.fn().mockResolvedValue(undefined), + }, + sidePanel: { + open: vi.fn().mockResolvedValue(undefined), + }, + scripting: { + executeScript: vi.fn().mockResolvedValue([{ result: undefined }]), + }, + cookies: { + getAll: vi.fn().mockResolvedValue([]), + remove: vi.fn().mockResolvedValue(undefined), + }, + alarms: { + create: vi.fn().mockResolvedValue(undefined), + onAlarm: { + addListener: vi.fn(), + removeListener: vi.fn(), + }, + }, + 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', + }, + }, +}; + +Object.defineProperty(globalThis, 'chrome', { value: webExtensionMock, writable: true }); +Object.defineProperty(globalThis, 'browser', { value: webExtensionMock, writable: true }); + +if (typeof window !== 'undefined') { + Object.defineProperty(window, 'chrome', { value: webExtensionMock, writable: true }); + Object.defineProperty(window, 'browser', { value: webExtensionMock, writable: true }); +} beforeEach(() => { vi.clearAllMocks(); - // Ensure storage mocks return objects even after clearAllMocks storageMock.local.get.mockResolvedValue({}); storageMock.session.get.mockResolvedValue({}); }); @@ -107,8 +131,7 @@ afterEach(() => { vi.restoreAllMocks(); }); -// 全局 matchMedia mock(ThemeModeProvider 依赖) -// 必须在 beforeEach 中设置,因为 afterEach 的 restoreAllMocks 会清除它 +// 全局 matchMedia 极客级环境模拟(ThemeModeProvider 依赖) beforeEach(() => { Object.defineProperty(window, 'matchMedia', { writable: true,