diff --git a/src/layout/FeatureNav/ThemeToggleButton.tsx b/src/layout/FeatureNav/ThemeToggleButton.tsx
new file mode 100644
index 0000000..aebcb22
--- /dev/null
+++ b/src/layout/FeatureNav/ThemeToggleButton.tsx
@@ -0,0 +1,20 @@
+import { Button } from '@/components/ui/button';
+import { useThemeToggle } from './useThemeToggle';
+
+export default function ThemeToggleButton() {
+ const { ThemeIcon, themeTitle, cycleThemeMode } = useThemeToggle();
+
+ return (
+
+ );
+}
diff --git a/src/layout/FeatureNav/__tests__/index.test.tsx b/src/layout/FeatureNav/__tests__/index.test.tsx
index b05ed70..be5d4d2 100644
--- a/src/layout/FeatureNav/__tests__/index.test.tsx
+++ b/src/layout/FeatureNav/__tests__/index.test.tsx
@@ -2,6 +2,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import type { PageType } from '@/types/storage';
import FeatureNav from '@/layout/FeatureNav';
+import { ThemeModeProvider } from '@/providers/ThemeModeProvider';
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query: string) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+});
const mockNavigateTo = vi.fn();
@@ -28,8 +43,15 @@ describe('FeatureNav 组件', () => {
mockRouterValue.visiblePages = ['timestamp', 'jwt', 'storageCleaner'];
});
+ const renderFeatureNav = () =>
+ render(
+
+
+ ,
+ );
+
it('应渲染全部可见工具图标', () => {
- render(
);
+ renderFeatureNav();
expect(screen.getByLabelText('JWT 解析')).toBeInTheDocument();
expect(screen.getByLabelText('时间戳')).toBeInTheDocument();
@@ -37,18 +59,24 @@ describe('FeatureNav 组件', () => {
});
it('点击图标应调用 navigateTo', () => {
- render(
);
+ renderFeatureNav();
fireEvent.click(screen.getByLabelText('JWT 解析'));
expect(mockNavigateTo).toHaveBeenCalledWith('jwt');
});
it('当前页对应项应有 active 样式与 aria-current', () => {
- render(
);
+ renderFeatureNav();
const activeButton = screen.getByLabelText('时间戳');
expect(activeButton).toHaveAttribute('aria-current', 'page');
expect(activeButton).toHaveClass('bg-muted');
expect(activeButton).toHaveClass('border-primary');
});
+
+ it('应渲染主题切换按钮', () => {
+ renderFeatureNav();
+
+ expect(screen.getByLabelText(/切换到/)).toBeInTheDocument();
+ });
});
diff --git a/src/layout/FeatureNav/index.tsx b/src/layout/FeatureNav/index.tsx
index f2cd436..0206bff 100644
--- a/src/layout/FeatureNav/index.tsx
+++ b/src/layout/FeatureNav/index.tsx
@@ -1,35 +1,39 @@
import { cn } from '@/lib/utils';
+import ThemeToggleButton from './ThemeToggleButton';
import { useFeatureNav } from './useFeatureNav';
export default function FeatureNav() {
const { navItems, currentPage, navigateTo } = useFeatureNav();
return (
-