diff --git a/src/components/theme-toggle/ThemeToggle.test.tsx b/src/components/theme-toggle/ThemeToggle.test.tsx index c2b7373..4e3ca4e 100644 --- a/src/components/theme-toggle/ThemeToggle.test.tsx +++ b/src/components/theme-toggle/ThemeToggle.test.tsx @@ -49,7 +49,7 @@ describe("ThemeToggle component", () => { }); - it('should display dark icon when in system mode and effective theme is dark', async () => { + it('should display system icon when theme is system', async () => { mockedUseTheme.mockReturnValue({ theme: Theme.SYSTEM, setTheme: vi.fn(), @@ -59,8 +59,8 @@ describe("ThemeToggle component", () => { await act(async () => { render(); }); - - expect(screen.getByText('🌙')).toBeInTheDocument(); + expect(screen.getByText('💻')).toBeInTheDocument(); + expect(screen.queryByText('🌙')).not.toBeInTheDocument(); expect(screen.queryByText('☀️️')).not.toBeInTheDocument(); }); @@ -75,8 +75,9 @@ describe("ThemeToggle component", () => { render(); }); - expect(screen.getByText('☀️')).toBeInTheDocument(); + expect(screen.getByText('💻')).toBeInTheDocument(); expect(screen.queryByText('🌙')).not.toBeInTheDocument(); + expect(screen.queryByText('☀️')).not.toBeInTheDocument(); }); it('should show all options when the dropdown is opened', async () => { @@ -106,4 +107,59 @@ describe("ThemeToggle component", () => { expect(lightOption).not.toHaveClass('themeOption--active'); expect(systemOption).not.toHaveClass('themeOption--active'); }); + + it('should not show the dropdown on initial render', async () => { + await act(async () => { + render(); + }); + + expect(screen.queryByText('Light')).not.toBeInTheDocument(); + expect(screen.queryByText('Dark')).not.toBeInTheDocument(); + expect(screen.queryByText('System')).not.toBeInTheDocument(); + }); + + it('should call setTheme with the selected value and close the dropdown', async () => { + const setTheme = vi.fn(); + mockedUseTheme.mockReturnValue({ + theme: Theme.DARK, + setTheme, + getEffectiveTheme: vi.fn(() => Theme.DARK), + }); + + await act(async () => { + render(); + }); + + fireEvent.click(screen.getByRole('button', { name: '🌙' })); + fireEvent.click(screen.getByText('Light').closest('button')!); + + expect(setTheme).toHaveBeenCalledWith(Theme.LIGHT); + expect(screen.queryByText('Light')).not.toBeInTheDocument(); + }); + + it('should close the dropdown when clicking outside', async () => { + await act(async () => { + render(); + }); + + fireEvent.click(screen.getByRole('button', { name: '🌙' })); + expect(screen.getByText('Light')).toBeInTheDocument(); + + fireEvent.mouseDown(document.body); + expect(screen.queryByText('Light')).not.toBeInTheDocument(); + }); + + it('should show a checkmark only on the active theme option', async () => { + await act(async () => { + render(); + }); + + fireEvent.click(screen.getByRole('button', { name: '🌙' })); + + const darkOption = screen.getByText('Dark').closest('button')!; + const lightOption = screen.getByText('Light').closest('button')!; + + expect(darkOption).toHaveTextContent('✓'); + expect(lightOption).not.toHaveTextContent('✓'); + }); }); diff --git a/src/components/theme-toggle/ThemeToggle.tsx b/src/components/theme-toggle/ThemeToggle.tsx index 7c825a4..319f442 100644 --- a/src/components/theme-toggle/ThemeToggle.tsx +++ b/src/components/theme-toggle/ThemeToggle.tsx @@ -1,14 +1,14 @@ import {useEffect, useRef, useState} from "react"; import {useTheme} from "../../context/hooks.tsx"; -import {Theme} from "../../context/theme/ThemeContextTypes.ts"; import {THEME_OPTIONS} from "../../constants/Theme.ts"; import styles from "./ThemeToggle.module.css" export function ThemeToggle() { - const { theme, setTheme, getEffectiveTheme } = useTheme(); + const { theme, setTheme } = useTheme(); const [themeMenuOpen, setThemeMenuOpen] = useState(false); const themeMenuRef = useRef(null); - const themeIcon = getEffectiveTheme() === Theme.DARK ? '🌙' : '☀️'; + const themeIcon = THEME_OPTIONS.find(o => o.value === theme)?.icon; + useEffect(() => { if (!themeMenuOpen) return;