pnpm add -D vitest @testing-library/react @testing-library/jest-dom jsdomVitest
RTL
React Testing Library
jest-axe
tsx
1// vitest.config.ts2import { defineConfig } from 'vitest/config';3import react from '@vitejs/plugin-react';4 5export default defineConfig({6 plugins: [react()],7 test: {8 environment: 'jsdom',9 globals: true,10 setupFiles: './test/setup.ts',11 },12});tsx
1// test/setup.ts2import '@testing-library/jest-dom';3import { vi } from 'vitest';4 5// Mock IntersectionObserver for useMotion6global.IntersectionObserver = vi.fn().mockImplementation(() => ({7 observe: vi.fn(),8 unobserve: vi.fn(),9 disconnect: vi.fn(),10}));11 12// Mock matchMedia for dark mode13Object.defineProperty(window, 'matchMedia', {14 value: vi.fn().mockImplementation((query) => ({15 matches: false,16 media: query,17 addEventListener: vi.fn(),18 removeEventListener: vi.fn(),19 })),20});tsx
1// Button.test.tsx2import { render, screen, fireEvent } from '@testing-library/react';3import { Button } from '@hua-labs/hua';4import { describe, it, expect, vi } from 'vitest';5 6describe('Button', () => {7 it('renders correctly', () => {8 render(<Button>Click me</Button>);9 expect(screen.getByRole('button')).toHaveTextContent('Click me');10 });11 12 it('handles click events', () => {13 const handleClick = vi.fn();14 render(<Button onClick={handleClick}>Click</Button>);15 16 fireEvent.click(screen.getByRole('button'));17 expect(handleClick).toHaveBeenCalledTimes(1);18 });19 20 it('applies variant styles', () => {21 render(<Button variant="outline">Outline</Button>);22 expect(screen.getByRole('button')).toHaveClass('border');23 });24});tsx
1// useMotion.test.tsx2import { renderHook } from '@testing-library/react';3import { useMotion } from '@hua-labs/hua/framework';4import { describe, it, expect } from 'vitest';5 6describe('useMotion', () => {7 it('returns ref and style', () => {8 const { result } = renderHook(() =>9 useMotion({ type: 'fadeIn', duration: 500 })10 );11 12 expect(result.current.ref).toBeDefined();13 expect(result.current.style).toBeDefined();14 });15 16 it('applies initial opacity for fadeIn', () => {17 const { result } = renderHook(() =>18 useMotion({ type: 'fadeIn' })19 );20 21 // Before intersection, opacity should be 022 expect(result.current.style.opacity).toBe(0);23 });24});tsx
1// i18n.test.tsx2import { render, screen } from '@testing-library/react';3import { I18nProvider, useTranslation } from '@hua-labs/hua';4import { describe, it, expect } from 'vitest';5 6const TestComponent = () => {7 const { t, currentLanguage } = useTranslation();8 return (9 <div>10 <span data-testid="lang">{currentLanguage}</span>11 <span data-testid="text">{t('common:hello')}</span>12 </div>13 );14};15 16const config = {17 defaultLanguage: 'ko',18 supportedLanguages: [19 { code: 'ko', name: 'Korean', nativeName: '한국어' },20 { code: 'en', name: 'English', nativeName: 'English' },21 ],22 namespaces: ['common'],23 loadPath: '/translations/{{lng}}/{{ns}}.json',24};25 26describe('useTranslation', () => {27 it('provides current language', () => {28 render(29 <I18nProvider config={config}>30 <TestComponent />31 </I18nProvider>32 );33 34 expect(screen.getByTestId('lang')).toHaveTextContent('ko');35 });36});tsx
1// accessibility.test.tsx2import { render } from '@testing-library/react';3import { axe, toHaveNoViolations } from 'jest-axe';4import { Button, Card, Input } from '@hua-labs/hua';5import { describe, it, expect } from 'vitest';6 7expect.extend(toHaveNoViolations);8 9describe('Accessibility', () => {10 it('Button has no a11y violations', async () => {11 const { container } = render(<Button>Click me</Button>);12 const results = await axe(container);13 expect(results).toHaveNoViolations();14 });15 16 it('Input with label has no a11y violations', async () => {17 const { container } = render(18 <div>19 <label htmlFor="email">Email</label>20 <Input id="email" type="email" />21 </div>22 );23 const results = await axe(container);24 expect(results).toHaveNoViolations();25 });26 27 it('Card with proper heading structure', async () => {28 const { container } = render(29 <Card>30 <h2>Card Title</h2>31 <p>Card content</p>32 </Card>33 );34 const results = await axe(container);35 expect(results).toHaveNoViolations();36 });37});