feat(TopBar): 添加搜索框快捷键提示功能及相关测试
- 新增 getSearchShortcutLabel 函数,根据平台返回搜索框的快捷键提示文案。 - 更新 SearchInput 组件,使用 getSearchShortcutLabel 显示正确的快捷键。 - 新增 constants.test.ts 测试文件,验证 getSearchShortcutLabel 函数在不同操作系统下的行为。 - 新增 features.lazy.test.ts 文件,测试懒加载功能,确保页面模块按需加载。
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
const pageLoadTracker = vi.hoisted(() => ({
|
||||||
|
loaded: [] as string[],
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/pages/Dashboard', () => {
|
||||||
|
pageLoadTracker.loaded.push('Dashboard');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/Timestamp', () => {
|
||||||
|
pageLoadTracker.loaded.push('Timestamp');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/StorageCleaner', () => {
|
||||||
|
pageLoadTracker.loaded.push('StorageCleaner');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/QrCode', () => {
|
||||||
|
pageLoadTracker.loaded.push('QrCode');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/TextStatistics', () => {
|
||||||
|
pageLoadTracker.loaded.push('TextStatistics');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/Jwt', () => {
|
||||||
|
pageLoadTracker.loaded.push('Jwt');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/JsonTools', () => {
|
||||||
|
pageLoadTracker.loaded.push('JsonTools');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/Base64Converter', () => {
|
||||||
|
pageLoadTracker.loaded.push('Base64Converter');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/RightClickRestorer', () => {
|
||||||
|
pageLoadTracker.loaded.push('RightClickRestorer');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
vi.mock('@/pages/TestDataGenerator', () => {
|
||||||
|
pageLoadTracker.loaded.push('TestDataGenerator');
|
||||||
|
return { default: () => null };
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('features 懒加载', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
pageLoadTracker.loaded.length = 0;
|
||||||
|
vi.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('仅导入工具函数时不应加载任何页面模块', async () => {
|
||||||
|
const { getAllFeatureKeys, getDefaultPageOrder } = await import('@/config/features');
|
||||||
|
|
||||||
|
getAllFeatureKeys();
|
||||||
|
getDefaultPageOrder();
|
||||||
|
|
||||||
|
expect(pageLoadTracker.loaded).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('访问 FEATURES 元数据时不应加载任何页面模块', async () => {
|
||||||
|
const { FEATURES } = await import('@/config/features');
|
||||||
|
|
||||||
|
expect(FEATURES).toHaveLength(10);
|
||||||
|
expect(pageLoadTracker.loaded).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('渲染 lazy 组件时才应加载对应页面模块', async () => {
|
||||||
|
const React = await import('react');
|
||||||
|
const { render, waitFor } = await import('@testing-library/react');
|
||||||
|
const { FEATURES } = await import('@/config/features');
|
||||||
|
|
||||||
|
const DashboardPage = FEATURES.find((f) => f.key === 'dashboard')!.components.popup;
|
||||||
|
|
||||||
|
render(
|
||||||
|
React.createElement(React.Suspense, { fallback: null }, React.createElement(DashboardPage)),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(pageLoadTracker.loaded).toEqual(['Dashboard']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import { type RefObject } from 'react';
|
|||||||
import { Search, X } from 'lucide-react';
|
import { Search, X } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { getSearchShortcutLabel } from './constants';
|
||||||
|
|
||||||
interface SearchInputProps {
|
interface SearchInputProps {
|
||||||
inputRef: RefObject<HTMLInputElement | null>;
|
inputRef: RefObject<HTMLInputElement | null>;
|
||||||
@@ -36,7 +37,7 @@ export default function SearchInput({
|
|||||||
/>
|
/>
|
||||||
{!searchQuery && (
|
{!searchQuery && (
|
||||||
<kbd className="pointer-events-none absolute right-3 top-1/2 hidden h-5 -translate-y-1/2 items-center gap-0.5 rounded border border-border/60 bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground/60 sm:inline-flex">
|
<kbd className="pointer-events-none absolute right-3 top-1/2 hidden h-5 -translate-y-1/2 items-center gap-0.5 rounded border border-border/60 bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground/60 sm:inline-flex">
|
||||||
⌘K
|
{getSearchShortcutLabel()}
|
||||||
</kbd>
|
</kbd>
|
||||||
)}
|
)}
|
||||||
{searchQuery && (
|
{searchQuery && (
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { getSearchShortcutLabel } from '@/layout/TopBar/constants';
|
||||||
|
|
||||||
|
describe('getSearchShortcutLabel', () => {
|
||||||
|
const originalUserAgent = navigator.userAgent;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.stubGlobal('navigator', { ...navigator, userAgent: originalUserAgent });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('macOS 应显示 ⌘K', () => {
|
||||||
|
vi.stubGlobal('navigator', {
|
||||||
|
...navigator,
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
});
|
||||||
|
expect(getSearchShortcutLabel()).toBe('⌘K');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Windows 应显示 Ctrl+K', () => {
|
||||||
|
vi.stubGlobal('navigator', {
|
||||||
|
...navigator,
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
});
|
||||||
|
expect(getSearchShortcutLabel()).toBe('Ctrl+K');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Linux 应显示 Ctrl+K', () => {
|
||||||
|
vi.stubGlobal('navigator', {
|
||||||
|
...navigator,
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
});
|
||||||
|
expect(getSearchShortcutLabel()).toBe('Ctrl+K');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,3 +3,9 @@ export const SEARCH_HISTORY_DISPLAY = 5;
|
|||||||
|
|
||||||
export const isSearchHistory = (val: unknown): val is string[] =>
|
export const isSearchHistory = (val: unknown): val is string[] =>
|
||||||
Array.isArray(val) && val.every((item) => typeof item === 'string');
|
Array.isArray(val) && val.every((item) => typeof item === 'string');
|
||||||
|
|
||||||
|
/** 根据平台返回搜索框快捷键提示文案 */
|
||||||
|
export function getSearchShortcutLabel(): string {
|
||||||
|
if (typeof navigator === 'undefined') return 'Ctrl+K';
|
||||||
|
return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent) ? '⌘K' : 'Ctrl+K';
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user