refactor(代码重复): 抽取共享工具函数与 UI 组件,消除多处重复实现

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
雨霖铃
2026-06-19 20:55:39 +08:00
parent 47d74f999e
commit 0f3060dcf3
24 changed files with 386 additions and 309 deletions
+4 -4
View File
@@ -77,15 +77,15 @@ describe('chromeStorage', () => {
describe('get 类型签名', () => {
it('无默认值时应推断为可选返回类型', () => {
const getWithoutDefault = () => storageUtil.get('app/theme');
expectTypeOf<ReturnType<typeof getWithoutDefault>>().toEqualTypeOf<
const _getWithoutDefault = () => storageUtil.get('app/theme');
expectTypeOf<ReturnType<typeof _getWithoutDefault>>().toEqualTypeOf<
Promise<string | undefined>
>();
});
it('有默认值时应推断为确定返回类型', () => {
const getWithDefault = () => storageUtil.get('app/theme', 'light');
expectTypeOf<ReturnType<typeof getWithDefault>>().toEqualTypeOf<Promise<string>>();
const _getWithDefault = () => storageUtil.get('app/theme', 'light');
expectTypeOf<ReturnType<typeof _getWithDefault>>().toEqualTypeOf<Promise<string>>();
});
});
@@ -0,0 +1,49 @@
import {
isRestrictedUrl,
isUnsupportedPageUrl,
RESTRICTED_PROTOCOLS,
} from '@/utils/restrictedUrls';
describe('restrictedUrls', () => {
describe('isRestrictedUrl', () => {
it('应识别受限协议页面', () => {
expect(isRestrictedUrl('chrome://settings')).toBe(true);
expect(isRestrictedUrl('chrome-extension://abc123/background.html')).toBe(true);
expect(isRestrictedUrl('about:blank')).toBe(true);
expect(isRestrictedUrl('edge://settings')).toBe(true);
expect(isRestrictedUrl('brave://settings')).toBe(true);
expect(isRestrictedUrl('view-source:https://example.com')).toBe(true);
expect(isRestrictedUrl('file:///path/to/file')).toBe(true);
expect(isRestrictedUrl('data:text/html,<h1>Hello</h1>')).toBe(true);
});
it('应允许普通 http/https 页面', () => {
expect(isRestrictedUrl('http://example.com')).toBe(false);
expect(isRestrictedUrl('https://example.com')).toBe(false);
});
it('空 URL 应视为受限', () => {
expect(isRestrictedUrl(undefined)).toBe(true);
expect(isRestrictedUrl('')).toBe(true);
});
});
describe('isUnsupportedPageUrl', () => {
it('应通过 protocol 精确匹配识别受限页面', () => {
expect(isUnsupportedPageUrl('chrome://newtab/')).toBe(true);
expect(isUnsupportedPageUrl('brave://settings/')).toBe(true);
expect(isUnsupportedPageUrl('https://example.com')).toBe(false);
});
it('无效 URL 应视为不支持', () => {
expect(isUnsupportedPageUrl(undefined)).toBe(true);
expect(isUnsupportedPageUrl('not-a-url')).toBe(true);
});
});
it('RESTRICTED_PROTOCOLS 应包含 storage cleaner 与右键恢复所需协议', () => {
expect(RESTRICTED_PROTOCOLS).toEqual(
expect.arrayContaining(['brave:', 'view-source:', 'file:', 'data:']),
);
});
});
+33
View File
@@ -0,0 +1,33 @@
import { getSyncSnapshot } from '@/utils/syncSnapshot';
describe('getSyncSnapshot', () => {
beforeEach(() => {
localStorage.clear();
vi.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
it('无快照时应返回默认值', () => {
expect(getSyncSnapshot('app/test-key', 'default')).toBe('default');
});
it('应读取并解析合法 JSON 快照', () => {
localStorage.setItem('snapshot/app/test-key', JSON.stringify('saved'));
expect(getSyncSnapshot('app/test-key', 'default')).toBe('saved');
});
it('validator 失败时应回退到默认值', () => {
localStorage.setItem('snapshot/app/test-key', JSON.stringify('invalid'));
const isNumber = (val: unknown): val is number => typeof val === 'number';
expect(getSyncSnapshot('app/test-key', 0, isNumber)).toBe(0);
});
it('非法 JSON 时应回退到默认值并记录错误', () => {
localStorage.setItem('snapshot/app/test-key', '{invalid');
expect(getSyncSnapshot('app/test-key', 'fallback')).toBe('fallback');
expect(console.error).toHaveBeenCalled();
});
});
+33
View File
@@ -0,0 +1,33 @@
/** 浏览器内部/受限协议(含尾部冒号,用于 protocol 匹配) */
export const RESTRICTED_PROTOCOLS = [
'chrome:',
'chrome-extension:',
'about:',
'edge:',
'brave:',
'view-source:',
'file:',
'data:',
] as const;
export function getUrlProtocol(url: string): string | null {
try {
return new URL(url).protocol;
} catch {
return null;
}
}
/** 用于 content script / 右键恢复等(protocol 精确匹配) */
export function isUnsupportedPageUrl(url: string | undefined): boolean {
if (!url) return true;
const protocol = getUrlProtocol(url);
if (!protocol) return true;
return (RESTRICTED_PROTOCOLS as readonly string[]).includes(protocol);
}
/** 用于 storage cleaner tab 检测(前缀匹配,兼容无 protocol 的场景) */
export function isRestrictedUrl(url?: string): boolean {
if (!url) return true;
return RESTRICTED_PROTOCOLS.some((p) => url.startsWith(p));
}
+2 -34
View File
@@ -1,33 +1,6 @@
import type { CleaningResult, StorageCleanerOptions, StorageCleanResult } from '@/types/storage';
const RESTRICTED_PROTOCOLS = [
'chrome:',
'chrome-extension:',
'about:',
'edge:',
'view-source:',
'file:',
'data:',
] as const;
/** 清理选项的 key 列表(用于遍历结果) */
const CLEAN_OPTION_KEYS: (keyof StorageCleanerOptions)[] = [
'localStorage',
'sessionStorage',
'indexedDB',
'cookies',
'cacheStorage',
'serviceWorkers',
];
const OPTION_LABELS: Record<string, string> = {
localStorage: 'Local Storage',
sessionStorage: 'Session Storage',
indexedDB: '站点存储',
cookies: 'Cookies',
cacheStorage: 'Cache Storage',
serviceWorkers: 'Service Workers',
};
import { CLEAN_OPTION_KEYS, OPTION_LABELS } from '@/pages/StorageCleaner/constants';
export { isRestrictedUrl } from '@/utils/restrictedUrls';
export async function getCurrentTab() {
// For popup pages, we need to get the active tab from the browser window that triggered the popup.
@@ -52,11 +25,6 @@ export async function getCurrentTab() {
return fallbackTab;
}
export function isRestrictedUrl(url?: string): boolean {
if (!url) return true;
return RESTRICTED_PROTOCOLS.some((p) => url.startsWith(p));
}
export async function getCookieSize(url: string): Promise<number> {
try {
const cookies = await chrome.cookies.getAll({ url });
+21
View File
@@ -0,0 +1,21 @@
/**
* 从 localStorage 获取同步快照(用于消除异步加载产生的首屏闪烁)
*/
export function getSyncSnapshot<T>(
key: string,
defaultValue: T,
validator?: (val: unknown) => val is T,
): T {
try {
const val = localStorage.getItem(`snapshot/${key}`);
if (!val) return defaultValue;
const parsed = JSON.parse(val) as unknown;
if (validator) {
return validator(parsed) ? parsed : defaultValue;
}
return (parsed as T) ?? defaultValue;
} catch (error) {
console.error(`[SyncSnapshot] Failed to read snapshot/${key}:`, error);
return defaultValue;
}
}
+1 -21
View File
@@ -1,28 +1,8 @@
import { useEffect, useRef, useState } from 'react';
import { storageUtil } from '@/utils/chromeStorage';
import { getSyncSnapshot } from '@/utils/syncSnapshot';
import type { StorageSchema } from '@/types/storage';
/**
* 从 localStorage 获取同步快照(用于消除异步加载产生的首屏闪烁)
*/
const getSyncSnapshot = <T>(
key: string,
defaultValue: T,
validator?: (val: unknown) => val is T,
): T => {
try {
const val = localStorage.getItem(`snapshot/${key}`);
if (!val) return defaultValue;
const parsed = JSON.parse(val) as unknown;
if (validator) {
return validator(parsed) ? parsed : defaultValue;
}
return (parsed as T) ?? defaultValue;
} catch {
return defaultValue;
}
};
export const useStorageState = <K extends keyof StorageSchema>(
key: K,
defaultValue: StorageSchema[K],