feat(Dashboard): 重构仪表板功能组件和逻辑

- 新增 dashboardFeatures.ts 和 useDashboard.ts 文件,封装仪表板功能的解析和状态管理逻辑。
- 更新 Dashboard 组件,使用 useDashboard 钩子简化可见工具和最近使用工具的处理。
- 添加仪表板功能的单元测试,确保功能正确性。
- 更新 README.md,移除不再使用的 ToolCard 组件说明。
This commit is contained in:
雨霖铃
2026-06-19 09:26:30 +08:00
parent c69a26b469
commit 9097c48b1f
13 changed files with 338 additions and 71 deletions
@@ -0,0 +1,49 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import * as featureConfig from '@/config/features';
import { resolveDashboardFeatures } from '../dashboardFeatures';
describe('resolveDashboardFeatures', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it('应按 keys 顺序返回可见工具,并排除 dashboard', () => {
const items = resolveDashboardFeatures(['jwt', 'timestamp'], ['dashboard', 'timestamp', 'jwt']);
expect(items.map((item) => item.key)).toEqual(['jwt', 'timestamp']);
expect(items.map((item) => item.feature.label)).toEqual(['JWT 解析', '时间戳']);
});
it('缺少 themeColorKey 但有 icon 的工具仍应显示', () => {
const originalGetFeatureByKey = featureConfig.getFeatureByKey;
vi.spyOn(featureConfig, 'getFeatureByKey').mockImplementation((key) => {
const feature = originalGetFeatureByKey(key);
if (key === 'timestamp' && feature) {
return { ...feature, themeColorKey: undefined };
}
return feature;
});
const items = resolveDashboardFeatures(['timestamp'], ['dashboard', 'timestamp']);
expect(items.map((item) => item.key)).toEqual(['timestamp']);
});
it('应过滤不在 visiblePages 中的工具', () => {
const items = resolveDashboardFeatures(['jwt', 'timestamp'], ['dashboard', 'timestamp']);
expect(items.map((item) => item.key)).toEqual(['timestamp']);
});
it('visiblePages 无匹配工具时应返回空列表', () => {
const items = resolveDashboardFeatures(['jwt', 'timestamp'], ['dashboard']);
expect(items).toEqual([]);
});
it('应过滤缺少 icon 的工具', () => {
const items = resolveDashboardFeatures(['dashboard', 'timestamp'], ['dashboard', 'timestamp']);
expect(items.map((item) => item.key)).toEqual(['timestamp']);
});
});
@@ -0,0 +1,100 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getFeatureByKey } from '@/config/features';
import type { DashboardFeatureItem } from '../useDashboard';
import Index from '../index';
const mockNavigateTo = vi.fn();
const mockUseDashboard = vi.fn();
vi.mock('../useDashboard', () => ({
useDashboard: () => mockUseDashboard(),
}));
function buildFeatureItem(key: 'timestamp' | 'jwt'): DashboardFeatureItem {
const feature = getFeatureByKey(key)!;
return {
key,
feature: feature as DashboardFeatureItem['feature'],
};
}
describe('Dashboard 页面', () => {
beforeEach(() => {
vi.clearAllMocks();
mockUseDashboard.mockReturnValue({
visibleFeatures: [buildFeatureItem('timestamp'), buildFeatureItem('jwt')],
recentFeatures: [],
showRecent: false,
navigateTo: mockNavigateTo,
});
});
it('应渲染全部工具网格', () => {
render(<Index />);
expect(screen.getByText('全部工具')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '时间戳' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'JWT 解析' })).toBeInTheDocument();
});
it('点击工具卡片应触发 navigateTo', async () => {
const user = userEvent.setup();
render(<Index />);
await user.click(screen.getByRole('button', { name: '时间戳' }));
expect(mockNavigateTo).toHaveBeenCalledTimes(1);
expect(mockNavigateTo).toHaveBeenCalledWith('timestamp');
});
it('有最近使用数据时应显示最近使用区块', () => {
mockUseDashboard.mockReturnValue({
visibleFeatures: [buildFeatureItem('timestamp')],
recentFeatures: [buildFeatureItem('jwt')],
showRecent: true,
navigateTo: mockNavigateTo,
});
render(<Index />);
expect(screen.getByText('最近使用')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'JWT 解析' })).toBeInTheDocument();
});
it('无最近使用数据时不应显示最近使用区块', () => {
render(<Index />);
expect(screen.queryByText('最近使用')).not.toBeInTheDocument();
});
it('点击最近使用按钮应触发 navigateTo', async () => {
const user = userEvent.setup();
mockUseDashboard.mockReturnValue({
visibleFeatures: [buildFeatureItem('timestamp')],
recentFeatures: [buildFeatureItem('jwt')],
showRecent: true,
navigateTo: mockNavigateTo,
});
render(<Index />);
await user.click(screen.getByRole('button', { name: 'JWT 解析' }));
expect(mockNavigateTo).toHaveBeenCalledWith('jwt');
});
it('无可见工具时应显示空状态', () => {
mockUseDashboard.mockReturnValue({
visibleFeatures: [],
recentFeatures: [],
showRecent: false,
navigateTo: mockNavigateTo,
});
render(<Index />);
expect(screen.getByText('没有可用的工具')).toBeInTheDocument();
});
});
@@ -0,0 +1,78 @@
import { renderHook } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useRouter } from '@/providers/RouterProvider';
import { useDashboard } from '../useDashboard';
import type { PageType } from '@/types/storage';
vi.mock('@/providers/RouterProvider', () => ({
useRouter: vi.fn(),
}));
const mockNavigateTo = vi.fn();
function mockRouterState(
overrides: {
visiblePages?: PageType[];
pageOrder?: PageType[];
recentlyUsedTools?: PageType[];
} = {},
) {
vi.mocked(useRouter).mockReturnValue({
currentPage: 'dashboard',
visiblePages: overrides.visiblePages ?? ['dashboard', 'timestamp', 'jwt'],
pageOrder: overrides.pageOrder ?? ['jwt', 'timestamp'],
recentlyUsedTools: overrides.recentlyUsedTools ?? [],
isLoaded: true,
navigateTo: mockNavigateTo,
goHome: vi.fn(),
setVisiblePages: vi.fn(),
setPageOrder: vi.fn(),
});
}
describe('useDashboard', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('应透传 router 中的 navigateTo', () => {
mockRouterState();
const { result } = renderHook(() => useDashboard());
expect(result.current.navigateTo).toBe(mockNavigateTo);
});
it('recentlyUsedTools 有可见工具时应显示最近使用', () => {
mockRouterState({
visiblePages: ['dashboard', 'timestamp', 'jwt'],
recentlyUsedTools: ['timestamp', 'jwt'],
});
const { result } = renderHook(() => useDashboard());
expect(result.current.showRecent).toBe(true);
expect(result.current.recentFeatures.map((item) => item.key)).toEqual(['timestamp', 'jwt']);
});
it('recentlyUsedTools 为空时不应显示最近使用', () => {
mockRouterState({ recentlyUsedTools: [] });
const { result } = renderHook(() => useDashboard());
expect(result.current.showRecent).toBe(false);
expect(result.current.recentFeatures).toEqual([]);
});
it('最近使用中的不可见工具应被过滤', () => {
mockRouterState({
visiblePages: ['dashboard', 'timestamp'],
recentlyUsedTools: ['jwt', 'timestamp'],
});
const { result } = renderHook(() => useDashboard());
expect(result.current.showRecent).toBe(true);
expect(result.current.recentFeatures.map((item) => item.key)).toEqual(['timestamp']);
});
});