diff --git a/src/renderer/components/theme-provider.tsx b/src/renderer/components/theme-provider.tsx new file mode 100644 index 0000000..faf4cc9 --- /dev/null +++ b/src/renderer/components/theme-provider.tsx @@ -0,0 +1,6 @@ +import { ThemeProvider as NextThemesProvider } from 'next-themes'; +import type { ComponentProps } from 'react'; + +export function ThemeProvider({ children, ...props }: ComponentProps) { + return {children}; +} \ No newline at end of file diff --git a/src/renderer/components/theme-toggle.tsx b/src/renderer/components/theme-toggle.tsx new file mode 100644 index 0000000..fc16a74 --- /dev/null +++ b/src/renderer/components/theme-toggle.tsx @@ -0,0 +1,30 @@ +import { useTheme } from 'next-themes'; +import { Moon, Sun } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useEffect, useState } from 'react'; + +export function ThemeToggle() { + const { resolvedTheme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + + if (!mounted) { + return ( + + ); + } + + const isDark = resolvedTheme === 'dark'; + return ( + + ); +} \ No newline at end of file diff --git a/tests/component/theme-toggle.test.tsx b/tests/component/theme-toggle.test.tsx new file mode 100644 index 0000000..5bdd0be --- /dev/null +++ b/tests/component/theme-toggle.test.tsx @@ -0,0 +1,40 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ThemeProvider } from '@/components/theme-provider'; +import { ThemeToggle } from '@/components/theme-toggle'; + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}); + +describe('ThemeToggle', () => { + beforeEach(() => { + localStorage.clear(); + document.documentElement.classList.remove('dark', 'light'); + }); + + it('toggles between light and dark when clicked', async () => { + render( + + + + ); + const btn = screen.getByRole('button'); + expect(btn).toHaveAccessibleName(/toggle theme/i); + await userEvent.click(btn); + expect(document.documentElement.classList.contains('dark')).toBe(true); + await userEvent.click(btn); + expect(document.documentElement.classList.contains('light')).toBe(true); + }); +}); \ No newline at end of file