feat: add right-click restorer for current site
- Add RightClickRestorer page with active unlock trigger - Content script with dual-world strategy (isolated + main world) - Monkey patch preventDefault/stopPropagation via background executeScript - Overlay penetration for pointer-events blocking layers - Delegated main-world injection to avoid CSP restrictions - Async message handling for reliable state updates - i18n translations (zh/en) and unit tests
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import RightClickRestorerPage from '../index';
|
||||
|
||||
const mockUnlock = vi.fn();
|
||||
|
||||
vi.mock('../useRightClickRestorer', () => ({
|
||||
useRightClickRestorer: () => ({
|
||||
domain: 'example.com',
|
||||
isLoading: false,
|
||||
isUnlocked: false,
|
||||
unlock: mockUnlock,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('RightClickRestorerPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render current domain', () => {
|
||||
render(<RightClickRestorerPage />);
|
||||
expect(screen.getByText(/example\.com/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render locked status', () => {
|
||||
render(<RightClickRestorerPage />);
|
||||
expect(screen.getByText(/statusLocked/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call unlock when button clicked', () => {
|
||||
render(<RightClickRestorerPage />);
|
||||
const button = screen.getByRole('button');
|
||||
fireEvent.click(button);
|
||||
expect(mockUnlock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useRightClickRestorer } from '../useRightClickRestorer';
|
||||
import { sendMessageToContent } from '@/utils/messages';
|
||||
|
||||
const mockTabsQuery = vi.fn();
|
||||
|
||||
vi.mock('@/utils/messages', () => ({
|
||||
MessageAction: {
|
||||
RESTORE_RIGHT_CLICK: 'restoreRightClick',
|
||||
QUERY_RIGHT_CLICK_STATUS: 'queryRightClickStatus',
|
||||
},
|
||||
sendMessageToContent: vi.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockTabsQuery.mockResolvedValue([{ url: 'https://example.com/path' }]);
|
||||
chrome.tabs.query = mockTabsQuery;
|
||||
vi.mocked(sendMessageToContent).mockResolvedValue({ success: true, restored: false });
|
||||
});
|
||||
|
||||
describe('useRightClickRestorer', () => {
|
||||
it('should load domain and query status', async () => {
|
||||
const { result } = renderHook(() => useRightClickRestorer());
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
expect(result.current.domain).toBe('example.com');
|
||||
expect(result.current.isUnlocked).toBe(false);
|
||||
expect(sendMessageToContent).toHaveBeenCalledWith('queryRightClickStatus');
|
||||
});
|
||||
|
||||
it('should unlock right click', async () => {
|
||||
vi.mocked(sendMessageToContent).mockResolvedValueOnce({ success: true, restored: false });
|
||||
vi.mocked(sendMessageToContent).mockResolvedValueOnce({ success: true, restored: true });
|
||||
|
||||
const { result } = renderHook(() => useRightClickRestorer());
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.unlock();
|
||||
});
|
||||
|
||||
expect(result.current.isUnlocked).toBe(true);
|
||||
expect(sendMessageToContent).toHaveBeenLastCalledWith('restoreRightClick');
|
||||
});
|
||||
|
||||
it('should handle sendMessage failure gracefully', async () => {
|
||||
vi.mocked(sendMessageToContent).mockRejectedValue(new Error('Connection failed'));
|
||||
|
||||
const { result } = renderHook(() => useRightClickRestorer());
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.unlock();
|
||||
});
|
||||
|
||||
expect(result.current.isUnlocked).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user