diff --git a/src/components/ui/ThemeToggle.test.tsx b/src/components/ui/ThemeToggle.test.tsx
new file mode 100644
index 0000000..f86985f
--- /dev/null
+++ b/src/components/ui/ThemeToggle.test.tsx
@@ -0,0 +1,71 @@
+// @vitest-environment jsdom
+import { describe, it, expect, beforeEach } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { ThemeToggle } from './ThemeToggle';
+
+describe('ThemeToggle', () => {
+ beforeEach(() => {
+ // Reset DOM and localStorage before each test
+ document.documentElement.classList.remove('dark');
+ localStorage.clear();
+ });
+
+ it('renders three buttons: Light, Dark, System', () => {
+ render();
+ expect(screen.getByRole('button', { name: /light/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /dark/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /system/i })).toBeInTheDocument();
+ });
+
+ it('default state is System (aria-pressed="true" on System button) when localStorage is empty', () => {
+ render();
+ const systemButton = screen.getByRole('button', { name: /system/i });
+ const lightButton = screen.getByRole('button', { name: /light/i });
+ const darkButton = screen.getByRole('button', { name: /dark/i });
+ expect(systemButton).toHaveAttribute('aria-pressed', 'true');
+ expect(lightButton).toHaveAttribute('aria-pressed', 'false');
+ expect(darkButton).toHaveAttribute('aria-pressed', 'false');
+ });
+
+ it('clicking Dark adds .dark class to document.documentElement', async () => {
+ const user = userEvent.setup();
+ render();
+ const darkButton = screen.getByRole('button', { name: /dark/i });
+ await user.click(darkButton);
+ expect(document.documentElement.classList.contains('dark')).toBe(true);
+ });
+
+ it('clicking Light removes .dark class from document.documentElement', async () => {
+ const user = userEvent.setup();
+ // Start with dark class applied
+ document.documentElement.classList.add('dark');
+ render();
+ const lightButton = screen.getByRole('button', { name: /light/i });
+ await user.click(lightButton);
+ expect(document.documentElement.classList.contains('dark')).toBe(false);
+ });
+
+ it('clicking Dark writes "dark" to localStorage under key "r2b-theme"', async () => {
+ const user = userEvent.setup();
+ render();
+ const darkButton = screen.getByRole('button', { name: /dark/i });
+ await user.click(darkButton);
+ expect(localStorage.getItem('r2b-theme')).toBe('dark');
+ });
+
+ it('active button has aria-pressed="true", others have aria-pressed="false"', async () => {
+ const user = userEvent.setup();
+ render();
+ const darkButton = screen.getByRole('button', { name: /dark/i });
+ await user.click(darkButton);
+ expect(darkButton).toHaveAttribute('aria-pressed', 'true');
+ expect(screen.getByRole('button', { name: /light/i })).toHaveAttribute('aria-pressed', 'false');
+ expect(screen.getByRole('button', { name: /system/i })).toHaveAttribute('aria-pressed', 'false');
+ });
+
+ it('component group has aria-label="Theme"', () => {
+ render();
+ expect(screen.getByRole('group', { name: 'Theme' })).toBeInTheDocument();
+ });
+});