# Testing Reference Guide for testing Mantine applications with Vitest and React Testing Library. ## Installation ```bash npm install -D vitest jsdom @testing-library/dom @testing-library/jest-dom @testing-library/react @testing-library/user-event ``` ## Vitest Configuration Add to `vite.config.ts`: ```ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: './vitest.setup.mjs', }, }); ``` ## Setup File Create `vitest.setup.mjs`: ```js import '@testing-library/jest-dom/vitest'; import { vi } from 'vitest'; // Fix for getComputedStyle const { getComputedStyle } = window; window.getComputedStyle = (elt) => getComputedStyle(elt); // Mock scrollIntoView window.HTMLElement.prototype.scrollIntoView = () => {}; // Mock matchMedia (required by Mantine) 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(), })), }); // Mock ResizeObserver (required by some Mantine components) class ResizeObserver { observe() {} unobserve() {} disconnect() {} } window.ResizeObserver = ResizeObserver; ``` ## Custom Render Function All Mantine components require MantineProvider. Create custom render: ```tsx // test-utils/render.tsx import { render as testingLibraryRender } from '@testing-library/react'; import { MantineProvider } from '@mantine/core'; import { theme } from '../src/theme'; // Your theme if any export function render(ui: React.ReactNode) { return testingLibraryRender(<>{ui}, { wrapper: ({ children }) => ( {children} ), }); } ``` ### Important: env="test" Setting `env="test"` on MantineProvider: - Disables CSS transitions (tests run faster) - Disables portals (elements render in place, easier to query) ## Export Test Utilities ```tsx // test-utils/index.ts import userEvent from '@testing-library/user-event'; export * from '@testing-library/react'; export { render } from './render'; export { userEvent }; ``` ## Writing Tests ### Basic Component Test ```tsx // Button.test.tsx import { render, screen } from '../test-utils'; import { Button } from '@mantine/core'; describe('Button', () => { it('renders children', () => { render(); expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument(); }); it('handles click events', async () => { const onClick = vi.fn(); render(); await userEvent.click(screen.getByRole('button')); expect(onClick).toHaveBeenCalledTimes(1); }); }); ``` ### Testing Form Inputs ```tsx import { render, screen } from '../test-utils'; import { userEvent } from '../test-utils'; import { TextInput } from '@mantine/core'; describe('TextInput', () => { it('accepts user input', async () => { const onChange = vi.fn(); render(); const input = screen.getByLabelText('Name'); await userEvent.type(input, 'John Doe'); expect(input).toHaveValue('John Doe'); expect(onChange).toHaveBeenCalled(); }); it('displays error', () => { render(); expect(screen.getByText('Invalid email')).toBeInTheDocument(); }); }); ``` ### Testing useForm ```tsx import { render, screen, waitFor } from '../test-utils'; import { userEvent } from '../test-utils'; import { useForm } from '@mantine/form'; import { TextInput, Button } from '@mantine/core'; function TestForm() { const form = useForm({ mode: 'uncontrolled', initialValues: { email: '' }, validate: { email: (v) => (!v.includes('@') ? 'Invalid email' : null), }, }); return (
console.log(values))}> ); } describe('Form', () => { it('validates on submit', async () => { render(); await userEvent.click(screen.getByRole('button', { name: /submit/i })); expect(screen.getByText('Invalid email')).toBeInTheDocument(); }); it('clears error on valid input', async () => { render(); await userEvent.type(screen.getByLabelText('Email'), 'test@email.com'); await userEvent.click(screen.getByRole('button', { name: /submit/i })); expect(screen.queryByText('Invalid email')).not.toBeInTheDocument(); }); }); ``` ### Testing Modals With `env="test"`, modals render in place (no portal): ```tsx import { render, screen } from '../test-utils'; import { userEvent } from '../test-utils'; import { useDisclosure } from '@mantine/hooks'; import { Modal, Button } from '@mantine/core'; function ModalDemo() { const [opened, { open, close }] = useDisclosure(false); return ( <> Modal Content ); } describe('Modal', () => { it('opens when button is clicked', async () => { render(); expect(screen.queryByText('Modal Content')).not.toBeInTheDocument(); await userEvent.click(screen.getByRole('button', { name: /open/i })); expect(screen.getByText('Modal Content')).toBeInTheDocument(); }); }); ``` ### Testing Select/Dropdown ```tsx import { render, screen } from '../test-utils'; import { userEvent } from '../test-utils'; import { Select } from '@mantine/core'; describe('Select', () => { it('selects an option', async () => { const onChange = vi.fn(); render(