refactor: 移除侧边栏相关功能和代码
This commit is contained in:
@@ -120,14 +120,3 @@ export function getAllFeatureKeys(): PageType[] {
|
|||||||
export function getDefaultPageOrder(): PageType[] {
|
export function getDefaultPageOrder(): PageType[] {
|
||||||
return FEATURES.filter((f) => f.key !== 'dashboard').map((f) => f.key);
|
return FEATURES.filter((f) => f.key !== 'dashboard').map((f) => f.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEntryPointType(): 'popup' | 'sidepanel' | 'tab' {
|
|
||||||
const pathname = window.location.pathname;
|
|
||||||
if (pathname.includes('sidepanel')) {
|
|
||||||
return 'sidepanel';
|
|
||||||
}
|
|
||||||
if (new URLSearchParams(window.location.search).get('mode') === 'tab') {
|
|
||||||
return 'tab';
|
|
||||||
}
|
|
||||||
return 'popup';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ export {
|
|||||||
getAllFeatureKeys,
|
getAllFeatureKeys,
|
||||||
getDefaultPageOrder,
|
getDefaultPageOrder,
|
||||||
getDefaultVisibleFeatureKeys,
|
getDefaultVisibleFeatureKeys,
|
||||||
getEntryPointType,
|
|
||||||
getFeatureByKey,
|
getFeatureByKey,
|
||||||
} from '@/config/featureMeta';
|
} from '@/config/featureMeta';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import '../../.wxt/types/imports.d.ts';
|
import '../../.wxt/types/imports.d.ts';
|
||||||
import { browser } from 'wxt/browser';
|
import { browser } from 'wxt/browser';
|
||||||
import { MessageAction, onMessage, sendMessage } from '@/utils/messages';
|
import { MessageAction, onMessage } from '@/utils/messages';
|
||||||
import { createAllContextMenus, parseContextMenuClick } from '@/utils/contextMenu';
|
import { createAllContextMenus, parseContextMenuClick } from '@/utils/contextMenu';
|
||||||
import { saveContextMenuData } from '@/utils/useContextMenuData';
|
import { saveContextMenuData } from '@/utils/useContextMenuData';
|
||||||
import { mainWorldInjectionScript } from '@/utils/rightClickInjection';
|
import { mainWorldInjectionScript } from '@/utils/rightClickInjection';
|
||||||
@@ -24,18 +24,6 @@ export default defineBackground(() => {
|
|||||||
|
|
||||||
const { featureKey, payload } = result.data;
|
const { featureKey, payload } = result.data;
|
||||||
|
|
||||||
try {
|
|
||||||
const sidePanelState = await browser.storage.local.get('sidePanelOpen');
|
|
||||||
const isSidePanelOpen = sidePanelState.sidePanelOpen === true;
|
|
||||||
|
|
||||||
if (isSidePanelOpen) {
|
|
||||||
await sendMessage(MessageAction.CONTEXT_MENU_CLICKED, { featureKey, payload });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.debug('[Context Menu] Side panel pipeline is not available:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveContextMenuData({ featureKey, payload });
|
await saveContextMenuData({ featureKey, payload });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -45,17 +33,6 @@ export default defineBackground(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 扩展图标点击时打开侧边栏
|
|
||||||
browser.action.onClicked.addListener(async (tab) => {
|
|
||||||
if (tab.id) {
|
|
||||||
try {
|
|
||||||
await browser.sidePanel.open({ tabId: tab.id });
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to open side panel via extension action clicked:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 注入主环境脚本(content script 无权访问 chrome.tabs/scripting,委托 background 执行)
|
// 3. 注入主环境脚本(content script 无权访问 chrome.tabs/scripting,委托 background 执行)
|
||||||
onMessage(MessageAction.INJECT_MAIN_WORLD_SCRIPT, async (message) => {
|
onMessage(MessageAction.INJECT_MAIN_WORLD_SCRIPT, async (message) => {
|
||||||
const sender = message.sender as chrome.runtime.MessageSender | undefined;
|
const sender = message.sender as chrome.runtime.MessageSender | undefined;
|
||||||
|
|||||||
@@ -2,30 +2,15 @@ import RouterProvider from '@/providers/RouterProvider';
|
|||||||
import TopBar from '@/layout/TopBar';
|
import TopBar from '@/layout/TopBar';
|
||||||
import RouterContainer from '@/components/RouterContainer';
|
import RouterContainer from '@/components/RouterContainer';
|
||||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||||
import { getEntryPointType } from '@/config/features';
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const entryType = getEntryPointType();
|
|
||||||
|
|
||||||
const routerConfig =
|
|
||||||
entryType === 'tab'
|
|
||||||
? {
|
|
||||||
syncKey: 'app/tabRoute' as const,
|
|
||||||
visiblePagesKey: 'app/tabVisiblePages' as const,
|
|
||||||
pageOrderKey: 'app/tabPageOrder' as const,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
syncKey: 'app/popupRoute' as const,
|
|
||||||
visiblePagesKey: 'app/popupVisiblePages' as const,
|
|
||||||
pageOrderKey: 'app/popupPageOrder' as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouterProvider
|
<RouterProvider
|
||||||
syncKey={routerConfig.syncKey}
|
syncKey="app/popupRoute"
|
||||||
visiblePagesKey={routerConfig.visiblePagesKey}
|
visiblePagesKey="app/popupVisiblePages"
|
||||||
pageOrderKey={routerConfig.pageOrderKey}
|
pageOrderKey="app/popupPageOrder"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col w-[400px] max-w-[400px] min-w-[400px] h-[600px] min-h-[600px] overflow-hidden bg-background sm:w-screen sm:max-w-none sm:min-w-0 sm:h-screen sm:min-h-0">
|
<div className="flex flex-col w-[400px] max-w-[400px] min-w-[400px] h-[600px] min-h-[600px] overflow-hidden bg-background">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<RouterContainer />
|
<RouterContainer />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Testing Tools - 标签页</title>
|
<title>Testing Tools</title>
|
||||||
<meta name="manifest.type" content="browser_action" />
|
<meta name="manifest.type" content="browser_action" />
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
@@ -33,13 +33,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
/* If opened in a tab (mode=tab), reset the fixed size */
|
|
||||||
@media screen and (min-width: 600px) {
|
|
||||||
html, body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import RouterProvider from '@/providers/RouterProvider';
|
|
||||||
import TopBar from '@/layout/TopBar';
|
|
||||||
import RouterContainer from '@/components/RouterContainer';
|
|
||||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
|
||||||
import { MessageAction, sendMessage } from '@/utils/messages';
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
useEffect(() => {
|
|
||||||
sendMessage(MessageAction.SIDE_PANEL_STATE_CHANGED, { isOpen: true });
|
|
||||||
return () => {
|
|
||||||
sendMessage(MessageAction.SIDE_PANEL_STATE_CHANGED, { isOpen: false });
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RouterProvider defaultRoute="dashboard" syncKey="app/sidepanelRoute">
|
|
||||||
<div className="flex flex-col h-screen w-full overflow-hidden">
|
|
||||||
<TopBar />
|
|
||||||
<ErrorBoundary>
|
|
||||||
<RouterContainer />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
</RouterProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Testing Tools - Side Panel</title>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
try {
|
|
||||||
var raw = localStorage.getItem('snapshot/app/themeMode');
|
|
||||||
var mode = raw ? JSON.parse(raw) : 'system';
|
|
||||||
if (mode !== 'light' && mode !== 'dark' && mode !== 'system') mode = 'system';
|
|
||||||
var isDark =
|
|
||||||
mode === 'dark' ||
|
|
||||||
(mode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
||||||
if (isDark) document.documentElement.classList.add('dark');
|
|
||||||
} catch (_error) {}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="./main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import AppRoot from '@/providers/AppRoot';
|
|
||||||
import '@/styles/shell.css';
|
|
||||||
import App from './App.tsx';
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
||||||
<AppRoot>
|
|
||||||
<App />
|
|
||||||
</AppRoot>,
|
|
||||||
);
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ArrowLeft, ExternalLink } from 'lucide-react';
|
import { ArrowLeft } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import SearchDropdown from './SearchDropdown';
|
import SearchDropdown from './SearchDropdown';
|
||||||
import SearchInput from './SearchInput';
|
import SearchInput from './SearchInput';
|
||||||
@@ -22,20 +22,11 @@ export default function TopBar() {
|
|||||||
handleSelectFeature,
|
handleSelectFeature,
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
cycleThemeMode,
|
cycleThemeMode,
|
||||||
handleOpenInTab,
|
|
||||||
goHome,
|
goHome,
|
||||||
clearSearch,
|
clearSearch,
|
||||||
} = useTopBar();
|
} = useTopBar();
|
||||||
|
|
||||||
const actions = [
|
const actions = [{ id: 'theme', icon: ThemeIcon, title: themeTitle, onClick: cycleThemeMode }];
|
||||||
{ id: 'theme', icon: ThemeIcon, title: themeTitle, onClick: cycleThemeMode },
|
|
||||||
{
|
|
||||||
id: 'open-in-tab',
|
|
||||||
icon: ExternalLink,
|
|
||||||
title: '在标签页打开',
|
|
||||||
onClick: () => void handleOpenInTab(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="relative z-50 flex h-14 items-center justify-between border-b border-border bg-background px-4">
|
<header className="relative z-50 flex h-14 items-center justify-between border-b border-border bg-background px-4">
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { useRouter } from '@/providers/RouterProvider';
|
|||||||
import { useThemeMode } from '@/providers/ThemeModeProvider';
|
import { useThemeMode } from '@/providers/ThemeModeProvider';
|
||||||
import { type FeatureConfig, FEATURES } from '@/config/features';
|
import { type FeatureConfig, FEATURES } from '@/config/features';
|
||||||
import { useStorageState } from '@/utils/useStorageState';
|
import { useStorageState } from '@/utils/useStorageState';
|
||||||
import { openExtensionPage } from '@/utils/chromeTabs';
|
|
||||||
import { isSearchHistory, SEARCH_HISTORY_DISPLAY, SEARCH_HISTORY_LIMIT } from './constants';
|
import { isSearchHistory, SEARCH_HISTORY_DISPLAY, SEARCH_HISTORY_LIMIT } from './constants';
|
||||||
|
|
||||||
export interface UseTopBarReturn {
|
export interface UseTopBarReturn {
|
||||||
@@ -30,7 +29,6 @@ export interface UseTopBarReturn {
|
|||||||
handleSelectFeature: (feature: FeatureConfig) => void;
|
handleSelectFeature: (feature: FeatureConfig) => void;
|
||||||
handleKeyDown: (e: ReactKeyboardEvent) => void;
|
handleKeyDown: (e: ReactKeyboardEvent) => void;
|
||||||
cycleThemeMode: () => void;
|
cycleThemeMode: () => void;
|
||||||
handleOpenInTab: () => Promise<void>;
|
|
||||||
goHome: () => void;
|
goHome: () => void;
|
||||||
clearSearch: () => void;
|
clearSearch: () => void;
|
||||||
}
|
}
|
||||||
@@ -51,11 +49,6 @@ export function useTopBar(): UseTopBarReturn {
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const handleOpenInTab = async () => {
|
|
||||||
await openExtensionPage('popup.html', { mode: 'tab' });
|
|
||||||
window.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||||
@@ -175,7 +168,6 @@ export function useTopBar(): UseTopBarReturn {
|
|||||||
handleSelectFeature,
|
handleSelectFeature,
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
cycleThemeMode,
|
cycleThemeMode,
|
||||||
handleOpenInTab,
|
|
||||||
goHome,
|
goHome,
|
||||||
clearSearch,
|
clearSearch,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|||||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
import Base64ConverterPage from '../index';
|
import Base64ConverterPage from '../index';
|
||||||
|
|
||||||
// Mock getEntryPointType
|
|
||||||
vi.mock('@/config/features', async (importOriginal) => {
|
|
||||||
const actual = await importOriginal<typeof import('@/config/features')>();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
getEntryPointType: () => 'sidepanel',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mock 子组件
|
// Mock 子组件
|
||||||
vi.mock('../components/TextMode', () => ({
|
vi.mock('../components/TextMode', () => ({
|
||||||
default: ({ onSwitchToImageMode }: { onSwitchToImageMode?: () => void }) => (
|
default: ({ onSwitchToImageMode }: { onSwitchToImageMode?: () => void }) => (
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ vi.mock('@/components/GlobalSnackbar', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@/config/features', async (importOriginal) => {
|
|
||||||
const actual = await importOriginal<typeof import('@/config/features')>();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
getEntryPointType: () => 'sidepanel',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock('../components/QrCodePreview', () => ({
|
vi.mock('../components/QrCodePreview', () => ({
|
||||||
default: () => <div data-testid="qr-code-preview">QrCodePreview</div>,
|
default: () => <div data-testid="qr-code-preview">QrCodePreview</div>,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ describe('RouterProvider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('应该从指定的 syncKey 加载路由', async () => {
|
it('应该从指定的 syncKey 加载路由', async () => {
|
||||||
mockStorageBatch({ 'app/sidepanelRoute': 'timestamp' });
|
mockStorageBatch({ 'app/popupRoute': 'timestamp' });
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<RouterProvider syncKey="app/sidepanelRoute">
|
<RouterProvider syncKey="app/popupRoute">
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</RouterProvider>,
|
</RouterProvider>,
|
||||||
);
|
);
|
||||||
@@ -118,31 +118,14 @@ describe('RouterProvider', () => {
|
|||||||
expect(storageUtil.set).toHaveBeenCalledWith('app/popupRoute', 'timestamp');
|
expect(storageUtil.set).toHaveBeenCalledWith('app/popupRoute', 'timestamp');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应该支持独立的标签页路由同步', async () => {
|
|
||||||
mockStorageBatch({ 'app/tabRoute': 'qrCode' });
|
|
||||||
|
|
||||||
render(
|
|
||||||
<RouterProvider syncKey="app/tabRoute">
|
|
||||||
<TestComponent />
|
|
||||||
</RouterProvider>,
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('current-page')).toHaveTextContent('qrCode');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('应该独立支持 visiblePagesKey 和 pageOrderKey,并合并缺失的新功能', async () => {
|
it('应该独立支持 visiblePagesKey 和 pageOrderKey,并合并缺失的新功能', async () => {
|
||||||
mockStorageBatch({
|
mockStorageBatch({
|
||||||
'app/sidepanelVisiblePages': ['timestamp', 'storageCleaner'],
|
'app/popupVisiblePages': ['timestamp', 'storageCleaner'],
|
||||||
'app/sidepanelPageOrder': ['storageCleaner', 'timestamp'],
|
'app/popupPageOrder': ['storageCleaner', 'timestamp'],
|
||||||
});
|
});
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<RouterProvider
|
<RouterProvider visiblePagesKey="app/popupVisiblePages" pageOrderKey="app/popupPageOrder">
|
||||||
visiblePagesKey="app/sidepanelVisiblePages"
|
|
||||||
pageOrderKey="app/sidepanelPageOrder"
|
|
||||||
>
|
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</RouterProvider>,
|
</RouterProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
Vendored
+1
-13
@@ -66,14 +66,10 @@ export interface FormMapEntry {
|
|||||||
* 定义了所有持久化在客户端的数据结构
|
* 定义了所有持久化在客户端的数据结构
|
||||||
*/
|
*/
|
||||||
export interface StorageSchema {
|
export interface StorageSchema {
|
||||||
/** RouterProvider 的默认路由键,仅当未显式传入 syncKey 时使用(popup/sidepanel/tab 入口已各自覆盖) */
|
/** RouterProvider 的默认路由键,仅当未显式传入 syncKey 时使用(popup 入口已覆盖) */
|
||||||
'app/currentRoute': PageType;
|
'app/currentRoute': PageType;
|
||||||
/** Popup 窗口的当前路由 */
|
/** Popup 窗口的当前路由 */
|
||||||
'app/popupRoute': PageType;
|
'app/popupRoute': PageType;
|
||||||
/** 侧边栏的当前路由 */
|
|
||||||
'app/sidepanelRoute': PageType;
|
|
||||||
/** 标签页的当前路由 */
|
|
||||||
'app/tabRoute': PageType;
|
|
||||||
/** RouterProvider 的默认可见页面列表键,仅当未显式传入 visiblePagesKey 时使用 */
|
/** RouterProvider 的默认可见页面列表键,仅当未显式传入 visiblePagesKey 时使用 */
|
||||||
'app/visiblePages': PageType[];
|
'app/visiblePages': PageType[];
|
||||||
/** RouterProvider 的默认页面排序键,仅当未显式传入 pageOrderKey 时使用 */
|
/** RouterProvider 的默认页面排序键,仅当未显式传入 pageOrderKey 时使用 */
|
||||||
@@ -82,14 +78,6 @@ export interface StorageSchema {
|
|||||||
'app/popupVisiblePages': PageType[];
|
'app/popupVisiblePages': PageType[];
|
||||||
/** Popup 窗口页面的显示顺序 */
|
/** Popup 窗口页面的显示顺序 */
|
||||||
'app/popupPageOrder': PageType[];
|
'app/popupPageOrder': PageType[];
|
||||||
/** 侧边栏可见的页面列表 */
|
|
||||||
'app/sidepanelVisiblePages': PageType[];
|
|
||||||
/** 侧边栏页面的显示顺序 */
|
|
||||||
'app/sidepanelPageOrder': PageType[];
|
|
||||||
/** 标签页可见的页面列表 */
|
|
||||||
'app/tabVisiblePages': PageType[];
|
|
||||||
/** 标签页页面的显示顺序 */
|
|
||||||
'app/tabPageOrder': PageType[];
|
|
||||||
/** 应用主题配置 */
|
/** 应用主题配置 */
|
||||||
'app/theme': string;
|
'app/theme': string;
|
||||||
/** 主题模式偏好(light/dark/system) */
|
/** 主题模式偏好(light/dark/system) */
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
|
||||||
import { openExtensionPage } from '@/utils/chromeTabs';
|
|
||||||
|
|
||||||
describe('chromeTabs', () => {
|
|
||||||
describe('openExtensionPage', () => {
|
|
||||||
it('应该在新标签页中打开扩展页面', async () => {
|
|
||||||
await openExtensionPage('popup.html');
|
|
||||||
|
|
||||||
expect(chrome.runtime.getURL).toHaveBeenCalledWith('popup.html');
|
|
||||||
expect(chrome.tabs.create).toHaveBeenCalledWith({
|
|
||||||
url: 'chrome-extension://test-extension-id/popup.html',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('当创建标签页失败时应记录错误', async () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
(chrome.tabs.create as any).mockRejectedValue(new Error('Tab creation failed'));
|
|
||||||
|
|
||||||
await openExtensionPage('popup.html');
|
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith('打开扩展页面失败:', expect.any(Error));
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -44,18 +44,21 @@ describe('messages', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应该支持不带数据的消息发送', async () => {
|
it('应该支持带数据的消息发送', async () => {
|
||||||
const mockSendMessage = await getMockSendMessage();
|
const mockSendMessage = await getMockSendMessage();
|
||||||
mockSendMessage.mockResolvedValue(undefined);
|
mockSendMessage.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const mockTab = { id: 456, url: 'https://example.com' };
|
const mockTab = { id: 456, url: 'https://example.com' };
|
||||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
||||||
|
|
||||||
await sendMessageToContent(MessageAction.SIDE_PANEL_STATE_CHANGED, { isOpen: true });
|
await sendMessageToContent(MessageAction.CONTEXT_MENU_CLICKED, {
|
||||||
|
featureKey: 'timestamp',
|
||||||
|
payload: '1234567890',
|
||||||
|
});
|
||||||
|
|
||||||
expect(mockSendMessage).toHaveBeenCalledWith(
|
expect(mockSendMessage).toHaveBeenCalledWith(
|
||||||
MessageAction.SIDE_PANEL_STATE_CHANGED,
|
MessageAction.CONTEXT_MENU_CLICKED,
|
||||||
{ isOpen: true },
|
{ featureKey: 'timestamp', payload: '1234567890' },
|
||||||
456,
|
456,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Chrome 标签页相关工具函数
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在新标签页中打开扩展页面
|
|
||||||
* @param page - 扩展页面路径(如 'popup.html')
|
|
||||||
* @param params - 可选的查询参数
|
|
||||||
*/
|
|
||||||
export async function openExtensionPage(
|
|
||||||
page: string,
|
|
||||||
params?: Record<string, string>,
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
const url = new URL(chrome.runtime.getURL(page));
|
|
||||||
if (params) {
|
|
||||||
Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value));
|
|
||||||
}
|
|
||||||
await chrome.tabs.create({ url: url.toString() });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('打开扩展页面失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { defineExtensionMessaging } from '@webext-core/messaging';
|
import { defineExtensionMessaging } from '@webext-core/messaging';
|
||||||
|
|
||||||
export enum MessageAction {
|
export enum MessageAction {
|
||||||
SIDE_PANEL_STATE_CHANGED = 'sidePanelStateChanged',
|
|
||||||
CONTEXT_MENU_CLICKED = 'contextMenuClicked',
|
CONTEXT_MENU_CLICKED = 'contextMenuClicked',
|
||||||
RESTORE_RIGHT_CLICK = 'restoreRightClick',
|
RESTORE_RIGHT_CLICK = 'restoreRightClick',
|
||||||
QUERY_RIGHT_CLICK_STATUS = 'queryRightClickStatus',
|
QUERY_RIGHT_CLICK_STATUS = 'queryRightClickStatus',
|
||||||
@@ -20,7 +19,6 @@ export interface ContextMenuClickedPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ProtocolMap {
|
export interface ProtocolMap {
|
||||||
[MessageAction.SIDE_PANEL_STATE_CHANGED](data: { isOpen: boolean }): void;
|
|
||||||
[MessageAction.CONTEXT_MENU_CLICKED](data: ContextMenuClickedPayload): void;
|
[MessageAction.CONTEXT_MENU_CLICKED](data: ContextMenuClickedPayload): void;
|
||||||
[MessageAction.RESTORE_RIGHT_CLICK](data: undefined): MessageResponse & { restored: boolean };
|
[MessageAction.RESTORE_RIGHT_CLICK](data: undefined): MessageResponse & { restored: boolean };
|
||||||
[MessageAction.QUERY_RIGHT_CLICK_STATUS](
|
[MessageAction.QUERY_RIGHT_CLICK_STATUS](
|
||||||
|
|||||||
+1
-12
@@ -41,15 +41,7 @@ export default defineConfig({
|
|||||||
name: 'Testing Tool',
|
name: 'Testing Tool',
|
||||||
description: 'A tool for testing web applications.',
|
description: 'A tool for testing web applications.',
|
||||||
version_name: undefined,
|
version_name: undefined,
|
||||||
permissions: [
|
permissions: ['storage', 'clipboardWrite', 'scripting', 'tabs', 'cookies', 'contextMenus'],
|
||||||
'storage',
|
|
||||||
'clipboardWrite',
|
|
||||||
'scripting',
|
|
||||||
'tabs',
|
|
||||||
'cookies',
|
|
||||||
'sidePanel',
|
|
||||||
'contextMenus',
|
|
||||||
],
|
|
||||||
host_permissions: ['<all_urls>'],
|
host_permissions: ['<all_urls>'],
|
||||||
icons: {
|
icons: {
|
||||||
'16': 'icon/16.png',
|
'16': 'icon/16.png',
|
||||||
@@ -66,9 +58,6 @@ export default defineConfig({
|
|||||||
'48': 'icon/48.png',
|
'48': 'icon/48.png',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
side_panel: {
|
|
||||||
default_path: 'entrypoints/sidepanel/index.html',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
vite: () => ({
|
vite: () => ({
|
||||||
plugins: [manualChunksForHtmlOnly()],
|
plugins: [manualChunksForHtmlOnly()],
|
||||||
|
|||||||
Reference in New Issue
Block a user