diff --git a/components/Toast.test.tsx b/components/Toast.test.tsx
new file mode 100644
index 0000000..ca23157
--- /dev/null
+++ b/components/Toast.test.tsx
@@ -0,0 +1,324 @@
+/**
+ * Component tests for Toast
+ * Tests individual toast rendering, variants, interactions, and accessibility
+ */
+
+import React from 'react';
+import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
+import { render, screen, act, fireEvent } from '@testing-library/react';
+import { axe, toHaveNoViolations } from 'jest-axe';
+import Toast from './Toast';
+import type { Toast as ToastType } from '@/lib/context/ToastContext';
+
+// Extend Vitest expect with jest-axe matchers
+expect.extend(toHaveNoViolations);
+
+describe('Toast', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ const mockToast: ToastType = {
+ id: 'test-toast-1',
+ variant: 'success',
+ title: 'Test Toast',
+ description: 'Test description',
+ duration: 5000,
+ };
+
+ const mockOnDismiss = vi.fn();
+
+ describe('Rendering', () => {
+ it('should render toast with title', () => {
+ render();
+ expect(screen.getByText('Test Toast')).toBeInTheDocument();
+ });
+
+ it('should render toast with description when provided', () => {
+ render();
+ expect(screen.getByText('Test description')).toBeInTheDocument();
+ });
+
+ it('should not render description when not provided', () => {
+ const toastWithoutDesc: ToastType = {
+ id: 'test-2',
+ variant: 'info',
+ title: 'No Description',
+ };
+ render();
+ expect(screen.queryByText('Test description')).not.toBeInTheDocument();
+ });
+
+ it('should render action button when provided', () => {
+ const actionClick = vi.fn();
+ const toastWithAction: ToastType = {
+ id: 'test-3',
+ variant: 'info',
+ title: 'With Action',
+ action: { label: 'Click Me', onClick: actionClick },
+ };
+ render();
+ expect(screen.getByText('Click Me')).toBeInTheDocument();
+ });
+
+ it('should render dismiss button', () => {
+ render();
+ const dismissButton = screen.getByLabelText('Dismiss notification');
+ expect(dismissButton).toBeInTheDocument();
+ });
+ });
+
+ describe('Variants', () => {
+ it('should render success variant', () => {
+ const successToast: ToastType = { ...mockToast, variant: 'success' };
+ const { container } = render();
+ const toastElement = container.querySelector('[role="status"]');
+ expect(toastElement).toHaveClass('border-status-success-border', 'bg-status-success-soft');
+ });
+
+ it('should render error variant', () => {
+ const errorToast: ToastType = { ...mockToast, variant: 'error' };
+ const { container } = render();
+ const toastElement = container.querySelector('[role="status"]');
+ expect(toastElement).toHaveClass('border-status-error-border', 'bg-status-error-soft');
+ });
+
+ it('should render warning variant', () => {
+ const warningToast: ToastType = { ...mockToast, variant: 'warning' };
+ const { container } = render();
+ const toastElement = container.querySelector('[role="status"]');
+ expect(toastElement).toHaveClass('border-status-warning-border', 'bg-status-warning-soft');
+ });
+
+ it('should render info variant', () => {
+ const infoToast: ToastType = { ...mockToast, variant: 'info' };
+ const { container } = render();
+ const toastElement = container.querySelector('[role="status"]');
+ expect(toastElement).toHaveClass('border-status-info-border', 'bg-status-info-soft');
+ });
+ });
+
+ describe('Accessibility', () => {
+ it('should have no accessibility violations', async () => {
+ const { container } = render();
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
+
+ it('should have role="status"', () => {
+ render();
+ const toast = screen.getByRole('status');
+ expect(toast).toBeInTheDocument();
+ });
+
+ it('should have aria-atomic="true"', () => {
+ render();
+ const toast = screen.getByRole('status');
+ expect(toast).toHaveAttribute('aria-atomic', 'true');
+ });
+
+ it('should have aria-hidden="true" on icon', () => {
+ render();
+ const icon = document.querySelector('[aria-hidden="true"]');
+ expect(icon).toBeInTheDocument();
+ });
+
+ it('should have aria-label on dismiss button', () => {
+ render();
+ const dismissButton = screen.getByLabelText('Dismiss notification');
+ expect(dismissButton).toBeInTheDocument();
+ });
+
+ it('should have focus-visible ring on dismiss button', () => {
+ render();
+ const dismissButton = screen.getByLabelText('Dismiss notification');
+ expect(dismissButton).toHaveClass('focus-visible:ring-2');
+ });
+ });
+
+ describe('Interactions', () => {
+ it('should call onDismiss when dismiss button is clicked', () => {
+ render();
+ const dismissButton = screen.getByLabelText('Dismiss notification');
+ fireEvent.click(dismissButton);
+ expect(mockOnDismiss).toHaveBeenCalledWith('test-toast-1');
+ });
+
+ it('should call action onClick when action button is clicked', () => {
+ const actionClick = vi.fn();
+ const toastWithAction: ToastType = {
+ ...mockToast,
+ action: { label: 'Action', onClick: actionClick },
+ };
+ render();
+ const actionButton = screen.getByText('Action');
+ fireEvent.click(actionButton);
+ expect(actionClick).toHaveBeenCalledTimes(1);
+ });
+
+ it('should pause timer on mouse enter', () => {
+ render();
+ const toast = screen.getByRole('status');
+ fireEvent.mouseEnter(toast);
+ // Timer should be paused - verify by advancing timers
+ act(() => {
+ vi.advanceTimersByTime(6000);
+ });
+ expect(mockOnDismiss).not.toHaveBeenCalled();
+ });
+
+ it('should resume timer on mouse leave', () => {
+ render();
+ const toast = screen.getByRole('status');
+
+ // Pause
+ fireEvent.mouseEnter(toast);
+ act(() => {
+ vi.advanceTimersByTime(3000);
+ });
+ expect(mockOnDismiss).not.toHaveBeenCalled();
+
+ // Resume
+ fireEvent.mouseLeave(toast);
+ act(() => {
+ vi.advanceTimersByTime(5000);
+ });
+ expect(mockOnDismiss).toHaveBeenCalledWith('test-toast-1');
+ });
+
+ it('should pause timer on focus', () => {
+ render();
+ const toast = screen.getByRole('status');
+ fireEvent.focus(toast);
+ act(() => {
+ vi.advanceTimersByTime(6000);
+ });
+ expect(mockOnDismiss).not.toHaveBeenCalled();
+ });
+
+ it('should resume timer on blur', () => {
+ render();
+ const toast = screen.getByRole('status');
+
+ // Pause
+ fireEvent.focus(toast);
+ act(() => {
+ vi.advanceTimersByTime(3000);
+ });
+ expect(mockOnDismiss).not.toHaveBeenCalled();
+
+ // Resume
+ fireEvent.blur(toast);
+ act(() => {
+ vi.advanceTimersByTime(5000);
+ });
+ expect(mockOnDismiss).toHaveBeenCalledWith('test-toast-1');
+ });
+ });
+
+ describe('Auto-dismiss', () => {
+ it('should auto-dismiss after duration', () => {
+ render();
+ act(() => {
+ vi.advanceTimersByTime(5000);
+ });
+ expect(mockOnDismiss).toHaveBeenCalledWith('test-toast-1');
+ });
+
+ it('should not auto-dismiss when duration is 0', () => {
+ const persistentToast: ToastType = { ...mockToast, duration: 0 };
+ render();
+ act(() => {
+ vi.advanceTimersByTime(10000);
+ });
+ expect(mockOnDismiss).not.toHaveBeenCalled();
+ });
+
+ it('should use custom duration when provided', () => {
+ const customDurationToast: ToastType = { ...mockToast, duration: 3000 };
+ render();
+ act(() => {
+ vi.advanceTimersByTime(3000);
+ });
+ expect(mockOnDismiss).toHaveBeenCalledWith('test-toast-1');
+ });
+
+ it('should clear timer on unmount', () => {
+ const { unmount } = render();
+ unmount();
+ act(() => {
+ vi.advanceTimersByTime(5000);
+ });
+ expect(mockOnDismiss).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('Styling', () => {
+ it('should have correct base classes', () => {
+ const { container } = render();
+ const toast = container.querySelector('[role="status"]');
+ expect(toast).toHaveClass('flex', 'w-full', 'max-w-sm', 'items-start', 'gap-3', 'rounded-2xl');
+ });
+
+ it('should have shadow', () => {
+ const { container } = render();
+ const toast = container.querySelector('[role="status"]');
+ expect(toast).toHaveClass('shadow-lg');
+ });
+
+ it('should have backdrop blur', () => {
+ const { container } = render();
+ const toast = container.querySelector('[role="status"]');
+ expect(toast).toHaveClass('backdrop-blur-md');
+ });
+
+ it('should have animation classes', () => {
+ const { container } = render();
+ const toast = container.querySelector('[role="status"]');
+ expect(toast).toHaveClass('animate-slide-in-bottom');
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should handle very long titles', () => {
+ const longTitleToast: ToastType = {
+ ...mockToast,
+ title: 'A'.repeat(200),
+ };
+ render();
+ expect(screen.getByText('A'.repeat(200))).toBeInTheDocument();
+ });
+
+ it('should handle very long descriptions', () => {
+ const longDescToast: ToastType = {
+ ...mockToast,
+ description: 'B'.repeat(500),
+ };
+ render();
+ expect(screen.getByText('B'.repeat(500))).toBeInTheDocument();
+ });
+
+ it('should handle special characters in title', () => {
+ const specialToast: ToastType = {
+ ...mockToast,
+ title: 'Test ',
+ };
+ render();
+ expect(screen.getByText('Test ')).toBeInTheDocument();
+ });
+
+ it('should handle multiple rapid dismiss calls', () => {
+ render();
+ const dismissButton = screen.getByLabelText('Dismiss notification');
+ fireEvent.click(dismissButton);
+ fireEvent.click(dismissButton);
+ fireEvent.click(dismissButton);
+ expect(mockOnDismiss).toHaveBeenCalledTimes(3);
+ });
+ });
+});
diff --git a/components/ToastContext.test.tsx b/components/ToastContext.test.tsx
new file mode 100644
index 0000000..6ce72a0
--- /dev/null
+++ b/components/ToastContext.test.tsx
@@ -0,0 +1,314 @@
+/**
+ * Component tests for ToastContext
+ * Tests context provider, hook behavior, and accessibility
+ */
+
+import React from 'react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { render, screen, act } from '@testing-library/react';
+import { ToastProvider, useToast } from '@/lib/context/ToastContext';
+import { axe, toHaveNoViolations } from 'jest-axe';
+
+// Extend Vitest expect with jest-axe matchers
+expect.extend(toHaveNoViolations);
+
+describe('ToastContext', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('ToastProvider', () => {
+ it('should render children without errors', () => {
+ render(
+
+ Test Child
+
+ );
+ expect(screen.getByText('Test Child')).toBeInTheDocument();
+ });
+
+ it('should have no accessibility violations', async () => {
+ const { container } = render(
+
+ Test Content
+
+ );
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
+
+ it('should provide toast context to children', () => {
+ const TestComponent = () => {
+ const toast = useToast();
+ return
{typeof toast.toast === 'function' ? 'yes' : 'no'}
;
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('has-context')).toHaveTextContent('yes');
+ });
+ });
+
+ describe('useToast hook', () => {
+ it('should throw error when used outside ToastProvider', () => {
+ const TestComponent = () => {
+ useToast();
+ return Test
;
+ };
+
+ expect(() => render()).toThrow('useToast must be used within a ToastProvider');
+ });
+
+ it('should return toast context value', () => {
+ const TestComponent = () => {
+ const context = useToast();
+ return (
+
+
{typeof context.toast === 'function' ? 'yes' : 'no'}
+
{typeof context.dismiss === 'function' ? 'yes' : 'no'}
+
{Array.isArray(context.toasts) ? 'yes' : 'no'}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('has-toast')).toHaveTextContent('yes');
+ expect(screen.getByTestId('has-dismiss')).toHaveTextContent('yes');
+ expect(screen.getByTestId('has-toasts')).toHaveTextContent('yes');
+ });
+
+ it('should add toast when toast function is called', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
{toasts.length}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('0');
+
+ act(() => {
+ screen.getByText('Add Toast').click();
+ });
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
+ });
+
+ it('should generate unique toast IDs', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
+
{toasts.map(t => t.id).join(',')}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ act(() => {
+ screen.getByText('Add 1').click();
+ screen.getByText('Add 2').click();
+ });
+
+ const ids = screen.getByTestId('toast-ids').textContent?.split(',');
+ expect(ids).toHaveLength(2);
+ expect(ids?.[0]).not.toBe(ids?.[1]);
+ });
+
+ it('should dismiss toast when dismiss function is called', () => {
+ const TestComponent = () => {
+ const { toast, dismiss, toasts } = useToast();
+ const toastId = toast({ variant: 'error', title: 'Test' });
+ return (
+
+
+
{toasts.length}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
+
+ act(() => {
+ screen.getByText('Dismiss').click();
+ });
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('0');
+ });
+
+ it('should set default duration to 5000ms', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
{toasts[0]?.duration ?? 0}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ expect(screen.getByTestId('toast-duration')).toHaveTextContent('5000');
+ });
+
+ it('should set duration to 0 when action is provided', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
{toasts[0]?.duration ?? 0}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ expect(screen.getByTestId('toast-duration')).toHaveTextContent('0');
+ });
+
+ it('should use custom duration when provided', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
{toasts[0]?.duration ?? 0}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ expect(screen.getByTestId('toast-duration')).toHaveTextContent('10000');
+ });
+
+ it('should support all toast variants', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
+
+
+
{toasts.length}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ act(() => {
+ screen.getByText('Success').click();
+ screen.getByText('Error').click();
+ screen.getByText('Warning').click();
+ screen.getByText('Info').click();
+ });
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('4');
+ });
+
+ it('should include description when provided', () => {
+ const TestComponent = () => {
+ const { toast, toasts } = useToast();
+ return (
+
+
+
{toasts[0]?.description ?? ''}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ expect(screen.getByTestId('toast-description')).toHaveTextContent('Description');
+ });
+ });
+});
diff --git a/components/ToastRegion.test.tsx b/components/ToastRegion.test.tsx
new file mode 100644
index 0000000..b850e2a
--- /dev/null
+++ b/components/ToastRegion.test.tsx
@@ -0,0 +1,392 @@
+/**
+ * Component tests for ToastRegion
+ * Tests toast rendering, positioning, and accessibility
+ */
+
+import React from 'react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { render, screen, act, waitFor } from '@testing-library/react';
+import { axe, toHaveNoViolations } from 'jest-axe';
+import { ToastProvider, useToast } from '@/lib/context/ToastContext';
+import ToastRegion from './ToastRegion';
+
+// Extend Vitest expect with jest-axe matchers
+expect.extend(toHaveNoViolations);
+
+describe('ToastRegion', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ const renderWithProvider = (component: React.ReactNode) => {
+ return render({component});
+ };
+
+ describe('Rendering', () => {
+ it('should not render when no toasts are present', () => {
+ renderWithProvider();
+ expect(screen.queryByLabelText('Notifications')).not.toBeInTheDocument();
+ });
+
+ it('should render when toasts are present', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ expect(screen.getByLabelText('Notifications')).toBeInTheDocument();
+ });
+
+ it('should render up to 3 toasts', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add 1').click();
+ screen.getByText('Add 2').click();
+ screen.getByText('Add 3').click();
+ screen.getByText('Add 4').click();
+ });
+
+ // Should only render 3 toasts (limit in ToastRegion)
+ const toasts = screen.getAllByRole('status');
+ expect(toasts).toHaveLength(3);
+ });
+
+ it('should render toasts in reverse order (newest first)', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add First').click();
+ screen.getByText('Add Second').click();
+ });
+
+ const toasts = screen.getAllByRole('status');
+ expect(toasts[0]).toHaveTextContent('Second');
+ expect(toasts[1]).toHaveTextContent('First');
+ });
+ });
+
+ describe('Accessibility', () => {
+ it('should have no accessibility violations when empty', async () => {
+ const { container } = renderWithProvider();
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
+
+ it('should have no accessibility violations with toasts', async () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ const { container } = renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
+
+ it('should have aria-label="Notifications"', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const region = screen.getByLabelText('Notifications');
+ expect(region).toBeInTheDocument();
+ });
+
+ it('should render toasts with role="status"', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const toasts = screen.getAllByRole('status');
+ expect(toasts).toHaveLength(1);
+ });
+
+ it('should render toasts with aria-atomic="true"', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const toast = screen.getByRole('status');
+ expect(toast).toHaveAttribute('aria-atomic', 'true');
+ });
+ });
+
+ describe('Positioning', () => {
+ it('should have fixed positioning classes', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const region = screen.getByLabelText('Notifications');
+ expect(region).toHaveClass('fixed');
+ });
+
+ it('should have z-index for layering', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const region = screen.getByLabelText('Notifications');
+ expect(region).toHaveClass('z-[100]');
+ });
+ });
+
+ describe('Dismissal', () => {
+ it('should remove toast when dismiss is called', () => {
+ const TestComponent = () => {
+ const { toast, dismiss } = useToast();
+ const toastId = toast({ variant: 'success', title: 'Test' });
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ expect(screen.getAllByRole('status')).toHaveLength(1);
+
+ act(() => {
+ screen.getByText('Dismiss').click();
+ });
+
+ expect(screen.queryByRole('status')).not.toBeInTheDocument();
+ });
+
+ it('should update toast count when toasts are dismissed', () => {
+ const TestComponent = () => {
+ const { toast, dismiss, toasts } = useToast();
+ const toastId1 = toast({ variant: 'info', title: 'Toast 1' });
+ const toastId2 = toast({ variant: 'info', title: 'Toast 2' });
+ return (
+
+
+
+
+
{toasts.length}
+
+ );
+ };
+
+ renderWithProvider();
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('2');
+
+ act(() => {
+ screen.getByText('Dismiss 1').click();
+ });
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
+
+ act(() => {
+ screen.getByText('Dismiss 2').click();
+ });
+
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('0');
+ });
+ });
+
+ describe('Multiple Toasts', () => {
+ it('should render multiple toasts of different variants', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Success').click();
+ screen.getByText('Error').click();
+ screen.getByText('Warning').click();
+ });
+
+ const toasts = screen.getAllByRole('status');
+ expect(toasts).toHaveLength(3);
+ expect(screen.getByText('Success')).toBeInTheDocument();
+ expect(screen.getByText('Error')).toBeInTheDocument();
+ expect(screen.getByText('Warning')).toBeInTheDocument();
+ });
+
+ it('should render toasts with descriptions', () => {
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ expect(screen.getByText('Description text')).toBeInTheDocument();
+ });
+
+ it('should render toasts with actions', () => {
+ const actionClick = vi.fn();
+ const TestComponent = () => {
+ const { toast } = useToast();
+ return (
+
+
+
+
+ );
+ };
+
+ renderWithProvider();
+
+ act(() => {
+ screen.getByText('Add').click();
+ });
+
+ const actionButton = screen.getByText('Action Button');
+ expect(actionButton).toBeInTheDocument();
+
+ act(() => {
+ actionButton.click();
+ });
+
+ expect(actionClick).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/vitest.config.mts b/vitest.config.mts
index 6b5c80d..31edb61 100644
--- a/vitest.config.mts
+++ b/vitest.config.mts
@@ -13,16 +13,19 @@ export default defineConfig({
'tests/property/**/*.test.cjs',
'tests/session/**/*.test.ts',
'tests/session/**/*.test.cjs',
+ 'components/**/*.test.tsx',
],
- environment: 'node',
+ environment: 'jsdom',
globals: true,
+ setupFiles: ['./vitest.setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
- include: ['lib/contracts/**/*.ts', 'app/**/*.ts', 'lib/**/*.ts'],
+ include: ['lib/contracts/**/*.ts', 'app/**/*.ts', 'lib/**/*.ts', 'components/**/*.tsx'],
exclude: [
'lib/contracts/**/*.test.ts',
'tests/**',
+ 'components/**/*.test.tsx',
],
},
alias: {
diff --git a/vitest.setup.ts b/vitest.setup.ts
new file mode 100644
index 0000000..7b0828b
--- /dev/null
+++ b/vitest.setup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom';