From c98d09d14ce4fbac8648d354e8ea736b73bf99cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Fri, 19 Jun 2026 21:06:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(TopBar):=20=E6=B7=BB=E5=8A=A0=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=A1=86=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=8A=E7=9B=B8=E5=85=B3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getSearchShortcutLabel 函数,根据平台返回搜索框的快捷键提示文案。 - 更新 SearchInput 组件,使用 getSearchShortcutLabel 显示正确的快捷键。 - 新增 constants.test.ts 测试文件,验证 getSearchShortcutLabel 函数在不同操作系统下的行为。 - 新增 features.lazy.test.ts 文件,测试懒加载功能,确保页面模块按需加载。 --- src/config/__tests__/features.lazy.test.ts | 85 +++++++++++++++++++ src/layout/TopBar/SearchInput.tsx | 3 +- src/layout/TopBar/__tests__/constants.test.ts | 37 ++++++++ src/layout/TopBar/constants.ts | 6 ++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/config/__tests__/features.lazy.test.ts create mode 100644 src/layout/TopBar/__tests__/constants.test.ts diff --git a/src/config/__tests__/features.lazy.test.ts b/src/config/__tests__/features.lazy.test.ts new file mode 100644 index 0000000..6aba224 --- /dev/null +++ b/src/config/__tests__/features.lazy.test.ts @@ -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']); + }); + }); +}); diff --git a/src/layout/TopBar/SearchInput.tsx b/src/layout/TopBar/SearchInput.tsx index 9b4ecd5..689405c 100644 --- a/src/layout/TopBar/SearchInput.tsx +++ b/src/layout/TopBar/SearchInput.tsx @@ -2,6 +2,7 @@ import { type RefObject } from 'react'; import { Search, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { getSearchShortcutLabel } from './constants'; interface SearchInputProps { inputRef: RefObject; @@ -36,7 +37,7 @@ export default function SearchInput({ /> {!searchQuery && ( - ⌘K + {getSearchShortcutLabel()} )} {searchQuery && ( diff --git a/src/layout/TopBar/__tests__/constants.test.ts b/src/layout/TopBar/__tests__/constants.test.ts new file mode 100644 index 0000000..43395b2 --- /dev/null +++ b/src/layout/TopBar/__tests__/constants.test.ts @@ -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'); + }); +}); diff --git a/src/layout/TopBar/constants.ts b/src/layout/TopBar/constants.ts index 33d2a51..41cbb2b 100644 --- a/src/layout/TopBar/constants.ts +++ b/src/layout/TopBar/constants.ts @@ -3,3 +3,9 @@ export const SEARCH_HISTORY_DISPLAY = 5; export const isSearchHistory = (val: unknown): val is 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'; +}