diff --git a/src/components/PageSkeleton.tsx b/src/components/PageSkeleton.tsx index cf7ed82..35c5bdb 100644 --- a/src/components/PageSkeleton.tsx +++ b/src/components/PageSkeleton.tsx @@ -4,30 +4,6 @@ * 用于 Suspense fallback 和初始加载状态,提供平滑的视觉过渡 * 避免白屏闪烁,减少布局偏移 */ -interface PageSkeletonProps { - /** 骨架屏类型 */ - variant?: 'dashboard' | 'tool'; -} - -/** - * 仪表盘卡片骨架屏 - */ -function DashboardCardSkeleton() { - return ( -
-
-
-
-
-
-
-
-
-
-
-
- ); -} /** * 工具页面骨架屏 @@ -57,22 +33,9 @@ function ToolPageSkeleton() { /** * 页面加载骨架屏 - * - * @param props - PageSkeletonProps - * @returns 骨架屏 JSX 元素 */ -export default function PageSkeleton({ variant = 'dashboard' }: PageSkeletonProps) { - if (variant === 'tool') { - return ; - } - - return ( -
- {Array.from({ length: 6 }).map((_, index) => ( - - ))} -
- ); +export default function PageSkeleton() { + return ; } PageSkeleton.displayName = 'PageSkeleton'; diff --git a/src/components/RouterContainer.tsx b/src/components/RouterContainer.tsx index ef29af1..86186ab 100644 --- a/src/components/RouterContainer.tsx +++ b/src/components/RouterContainer.tsx @@ -30,7 +30,7 @@ function LoadedPage({ pageKey }: { pageKey: PageType }) { }, [pageKey]); if (!Page) { - return ; + return ; } return ; @@ -39,9 +39,6 @@ function LoadedPage({ pageKey }: { pageKey: PageType }) { export default function RouterContainer() { const { currentPage } = useRouter(); - const animationClass = - currentPage === 'dashboard' ? 'page-transition-dashboard' : 'page-transition-enter'; - const currentFeature = getFeatureByKey(currentPage); return ( @@ -49,8 +46,7 @@ export default function RouterContainer() { key={currentPage} className={cn( 'flex-1 flex flex-col overflow-x-hidden overflow-y-auto', - 'motion-reduce:transition-none', - animationClass, + 'motion-reduce:transition-none page-transition-enter', )} > diff --git a/src/components/__tests__/PageSkeleton.test.tsx b/src/components/__tests__/PageSkeleton.test.tsx index 34ae7bf..3839723 100644 --- a/src/components/__tests__/PageSkeleton.test.tsx +++ b/src/components/__tests__/PageSkeleton.test.tsx @@ -4,41 +4,17 @@ import PageSkeleton from '@/components/PageSkeleton'; describe('PageSkeleton 组件', () => { describe('渲染测试', () => { - it('默认应渲染 dashboard 骨架屏', () => { + it('应渲染工具页面骨架', () => { const { container } = render(); - // dashboard 骨架屏包含 6 个卡片 - const cards = container.querySelectorAll('.rounded-xl'); - expect(cards.length).toBe(6); - }); - - it('variant 为 dashboard 时应渲染仪表盘卡片骨架', () => { - const { container } = render(); - - // 每个卡片有 2 个骨架元素(图标、文本),6 个卡片共 12 个 - const cards = container.querySelectorAll('.rounded-xl'); - expect(cards.length).toBe(6); - }); - - it('variant 为 tool 时应渲染工具页面骨架', () => { - const { container } = render(); - - // tool 骨架屏包含标题、输入区、控制栏 3 个按钮、结果区 const skeletons = container.querySelectorAll('.animate-pulse'); expect(skeletons.length).toBeGreaterThan(0); }); }); describe('布局结构测试', () => { - it('dashboard 骨架屏应使用 grid 布局', () => { - const { container } = render(); - const gridContainer = container.firstChild as HTMLElement; - - expect(gridContainer).toHaveClass('grid'); - }); - - it('tool 骨架屏应有内边距', () => { - const { container } = render(); + it('工具骨架屏应有内边距', () => { + const { container } = render(); const toolContainer = container.firstChild as HTMLElement; expect(toolContainer).toHaveClass('p-5'); @@ -46,16 +22,8 @@ describe('PageSkeleton 组件', () => { }); describe('骨架屏元素测试', () => { - it('dashboard 骨架屏应包含圆角和边框样式', () => { - const { container } = render(); - - // 获取第一个卡片容器 - const card = container.querySelector('.rounded-xl.border'); - expect(card).toBeInTheDocument(); - }); - - it('tool 骨架屏应包含动画脉冲效果', () => { - const { container } = render(); + it('工具骨架屏应包含动画脉冲效果', () => { + const { container } = render(); const skeletons = container.querySelectorAll('.animate-pulse'); expect(skeletons.length).toBeGreaterThan(0); diff --git a/src/components/__tests__/RouterContainer.test.tsx b/src/components/__tests__/RouterContainer.test.tsx index 1691e8c..d2e170c 100644 --- a/src/components/__tests__/RouterContainer.test.tsx +++ b/src/components/__tests__/RouterContainer.test.tsx @@ -6,12 +6,10 @@ import type { PageType } from '@/types/storage'; import React from 'react'; const mockRouterValue = { - currentPage: 'dashboard' as PageType, - visiblePages: ['dashboard', 'timestamp'] as PageType[], + currentPage: 'timestamp' as PageType, + visiblePages: ['timestamp', 'jwt'] as PageType[], pageOrder: ['timestamp'] as PageType[], navigateTo: vi.fn(), - syncNavigation: vi.fn(), - goHome: vi.fn(), setVisiblePages: vi.fn(), setPageOrder: vi.fn(), recentlyUsedTools: [] as PageType[], @@ -34,21 +32,14 @@ describe('RouterContainer 组件', () => { describe('渲染测试', () => { it('mount 后应直接渲染页面结构(不等待 storage 加载)', () => { - mockRouterValue.currentPage = 'dashboard'; + mockRouterValue.currentPage = 'timestamp'; const { container } = renderWithProvider(); - expect(container.querySelector('.page-transition-dashboard')).toBeInTheDocument(); + expect(container.querySelector('.page-transition-enter')).toBeInTheDocument(); }); }); describe('动画类测试', () => { - it('在 dashboard 页面应应用 dashboard 动画类', () => { - mockRouterValue.currentPage = 'dashboard'; - renderWithProvider(); - const box = document.querySelector('.page-transition-dashboard'); - expect(box).toBeInTheDocument(); - }); - - it('在非 dashboard 页面应应用 enter 动画类', () => { + it('应应用 enter 动画类', () => { mockRouterValue.currentPage = 'timestamp'; renderWithProvider(); const box = document.querySelector('.page-transition-enter'); @@ -60,7 +51,7 @@ describe('RouterContainer 组件', () => { it('currentPage 变化时应更新', () => { const { rerender } = renderWithProvider(); - mockRouterValue.currentPage = 'timestamp'; + mockRouterValue.currentPage = 'jwt'; rerender({}); const box = document.querySelector('.page-transition-enter'); @@ -70,11 +61,10 @@ describe('RouterContainer 组件', () => { describe('页面级错误隔离', () => { it('PageErrorBoundary 应包裹页面内容', () => { - mockRouterValue.currentPage = 'dashboard'; + mockRouterValue.currentPage = 'timestamp'; const { container } = renderWithProvider(); - // 验证 RouterContainer 的 Box 结构存在 - const routerBox = container.querySelector('.page-transition-dashboard'); + const routerBox = container.querySelector('.page-transition-enter'); expect(routerBox).toBeInTheDocument(); }); }); diff --git a/src/config/__tests__/features.lazy.test.ts b/src/config/__tests__/features.lazy.test.ts index 5588792..69e9ed2 100644 --- a/src/config/__tests__/features.lazy.test.ts +++ b/src/config/__tests__/features.lazy.test.ts @@ -4,10 +4,6 @@ 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 }; @@ -63,24 +59,24 @@ describe('features 懒加载', () => { it('访问 FEATURES 元数据时不应加载任何页面模块', async () => { const { FEATURES } = await import('@/config/features'); - expect(FEATURES).toHaveLength(10); + expect(FEATURES).toHaveLength(9); expect(pageLoadTracker.loaded).toEqual([]); }); it('loadPage 应仅加载对应页面模块', async () => { const { loadPage } = await import('@/config/pageLoaders/index'); - await loadPage('dashboard'); + await loadPage('timestamp'); - expect(pageLoadTracker.loaded).toEqual(['Dashboard']); + expect(pageLoadTracker.loaded).toEqual(['Timestamp']); }); it('loadPage 切换页面时不应加载无关页面模块', async () => { const { loadPage } = await import('@/config/pageLoaders/index'); - await loadPage('timestamp'); + await loadPage('jwt'); - expect(pageLoadTracker.loaded).toEqual(['Timestamp']); + expect(pageLoadTracker.loaded).toEqual(['Jwt']); expect(pageLoadTracker.loaded).not.toContain('QrCode'); expect(pageLoadTracker.loaded).not.toContain('TestDataGenerator'); }); diff --git a/src/config/__tests__/features.test.ts b/src/config/__tests__/features.test.ts index d6b06d0..99d8949 100644 --- a/src/config/__tests__/features.test.ts +++ b/src/config/__tests__/features.test.ts @@ -9,8 +9,8 @@ import { describe('features', () => { describe('FEATURES', () => { - it('应该有10个功能定义', () => { - expect(FEATURES).toHaveLength(10); + it('应该有9个功能定义', () => { + expect(FEATURES).toHaveLength(9); }); it('应该有每个功能的所有必需属性', () => { @@ -24,12 +24,9 @@ describe('features', () => { expect(typeof feature.label).toBe('string'); expect(typeof feature.description).toBe('string'); expect(typeof feature.defaultVisible).toBe('boolean'); - - if (feature.key !== 'dashboard') { - expect(feature).toHaveProperty('icon'); - expect(feature).toHaveProperty('themeColorKey'); - expect(typeof feature.themeColorKey).toBe('string'); - } + expect(feature).toHaveProperty('icon'); + expect(feature).toHaveProperty('themeColorKey'); + expect(typeof feature.themeColorKey).toBe('string'); }); }); @@ -41,13 +38,6 @@ describe('features', () => { }); describe('getFeatureByKey', () => { - it('应该返回dashboard功能', () => { - const feature = getFeatureByKey('dashboard'); - expect(feature).toBeDefined(); - expect(feature?.key).toBe('dashboard'); - expect(feature?.label).toBe('仪表盘'); - }); - it('应该返回时间戳功能', () => { const feature = getFeatureByKey('timestamp'); expect(feature).toBeDefined(); @@ -78,9 +68,8 @@ describe('features', () => { }); }); - it('应该包含仪表盘、时间戳、存储清理、二维码', () => { + it('应该包含时间戳、存储清理、二维码', () => { const visibleKeys = getDefaultVisibleFeatureKeys(); - expect(visibleKeys).toContain('dashboard'); expect(visibleKeys).toContain('timestamp'); expect(visibleKeys).toContain('storageCleaner'); expect(visibleKeys).toContain('qrCode'); @@ -90,8 +79,7 @@ describe('features', () => { describe('getAllFeatureKeys', () => { it('应该返回所有功能key', () => { const allKeys = getAllFeatureKeys(); - expect(allKeys).toHaveLength(10); - expect(allKeys).toContain('dashboard'); + expect(allKeys).toHaveLength(9); expect(allKeys).toContain('timestamp'); expect(allKeys).toContain('storageCleaner'); expect(allKeys).toContain('qrCode'); @@ -105,9 +93,9 @@ describe('features', () => { }); describe('getDefaultPageOrder', () => { - it('应该排除仪表盘从页面顺序', () => { + it('应该包含所有工具页面', () => { const pageOrder = getDefaultPageOrder(); - expect(pageOrder).not.toContain('dashboard'); + expect(pageOrder).toHaveLength(9); }); it('应该包含时间戳、存储清理、二维码在页面顺序', () => { @@ -116,10 +104,5 @@ describe('features', () => { expect(pageOrder).toContain('storageCleaner'); expect(pageOrder).toContain('qrCode'); }); - - it('应该有9个项目在页面顺序', () => { - const pageOrder = getDefaultPageOrder(); - expect(pageOrder).toHaveLength(9); - }); }); }); diff --git a/src/config/featureMeta.ts b/src/config/featureMeta.ts index e2e2444..6a2637f 100644 --- a/src/config/featureMeta.ts +++ b/src/config/featureMeta.ts @@ -25,12 +25,6 @@ export interface FeatureConfig { } export const FEATURES: FeatureConfig[] = [ - { - key: 'dashboard', - label: '仪表盘', - description: '', - defaultVisible: true, - }, { key: 'timestamp', label: '时间戳', @@ -118,5 +112,5 @@ export function getAllFeatureKeys(): PageType[] { } export function getDefaultPageOrder(): PageType[] { - return FEATURES.filter((f) => f.key !== 'dashboard').map((f) => f.key); + return FEATURES.map((f) => f.key); } diff --git a/src/config/pageLoaders/dashboard.ts b/src/config/pageLoaders/dashboard.ts deleted file mode 100644 index e4b3377..0000000 --- a/src/config/pageLoaders/dashboard.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ComponentType } from 'react'; - -export default function loadDashboard(): Promise<{ default: ComponentType }> { - return import('@/pages/Dashboard'); -} diff --git a/src/config/pageLoaders/index.ts b/src/config/pageLoaders/index.ts index d1ff4ad..839504c 100644 --- a/src/config/pageLoaders/index.ts +++ b/src/config/pageLoaders/index.ts @@ -16,8 +16,6 @@ function ensurePagesStyles(): Promise { export function loadPage(key: PageType): Promise { return ensurePagesStyles().then(() => { switch (key) { - case 'dashboard': - return import('@/config/pageLoaders/dashboard').then((m) => m.default()); case 'timestamp': return import('@/config/pageLoaders/timestamp').then((m) => m.default()); case 'storageCleaner': diff --git a/src/entrypoints/popup/App.tsx b/src/entrypoints/popup/App.tsx index 9086530..c7b3915 100644 --- a/src/entrypoints/popup/App.tsx +++ b/src/entrypoints/popup/App.tsx @@ -1,20 +1,25 @@ import RouterProvider from '@/providers/RouterProvider'; import TopBar from '@/layout/TopBar'; +import FeatureNav from '@/layout/FeatureNav'; import RouterContainer from '@/components/RouterContainer'; import ErrorBoundary from '@/components/ErrorBoundary'; export default function App() { return ( -
- - - - +
+
+ + + + +
+
); diff --git a/src/entrypoints/popup/index.html b/src/entrypoints/popup/index.html index 98c4a48..1e8cc68 100644 --- a/src/entrypoints/popup/index.html +++ b/src/entrypoints/popup/index.html @@ -21,7 +21,7 @@