npm package: @fix-my/test-runner
Description: Jest/React Testing Library (RTL) 호환 API를 제공하는 경량 테스트 러너 패키지입니다.
Latest version: 0.3.0

# @fix-my/test-runner

Jest/React Testing Library (RTL) 호환 API를 제공하는 경량 테스트 러너 패키지입니다.

## 특징

- **Jest 호환 API**: `describe`, `it`, `expect` 등 익숙한 인터페이스
- **RTL 스타일 쿼리**: `screen.getByText()`, `screen.getByRole()` 등
- **풍부한 DOM 매처**: `toBeInTheDocument()`, `toBeVisible()`, `toHaveTextContent()` 등
- **듀얼 환경 지원**: Node.js VM과 브라우저 모두에서 실행 가능
- **TypeScript 지원**: 완전한 타입 정의 포함

## 설치

```bash
npm install @fix-my/test-runner
```

## 기본 사용법

```typescript
import { render, screen, expect, describe, it, cleanup } from '@fix-my/test-runner';

describe('Button Component', () => {
  afterEach(() => {
    cleanup();
  });

  it('renders with correct text', () => {
    render(<Button>Click me</Button>);

    const button = screen.getByText('Click me');
    expect(button).toBeInTheDocument();
    expect(button).toBeVisible();
  });

  it('displays loading state', async () => {
    render(<Button loading>Submit</Button>);

    const spinner = await screen.findByRole('img', { timeout: 1000 });
    expect(spinner).toBeInTheDocument();
  });
});
```

## API Reference

### Test Structure

#### `describe(name, fn)`
테스트 그룹을 정의합니다.

```typescript
describe('User Form', () => {
  // 테스트들...
});
```

#### `it(name, fn)` / `test(name, fn)`
개별 테스트 케이스를 정의합니다.

```typescript
it('validates email format', () => {
  // 테스트 로직...
});
```

### Rendering

#### `render(element)`
React 엘리먼트를 DOM에 렌더링합니다.

```typescript
const { container, unmount, rerender } = render(<App />);
```

**반환값:**
- `container`: 렌더링된 컨테이너 엘리먼트
- `unmount()`: 컴포넌트를 DOM에서 제거
- `rerender(newElement)`: 새 props로 리렌더링

#### `cleanup()`
모든 렌더링된 컴포넌트를 정리합니다.

```typescript
afterEach(() => {
  cleanup();
});
```

### Screen Queries

#### Query Types

| Query | Returns | Throws | Use Case |
|-------|---------|--------|----------|
| `getBy*` | Element | Yes | 엘리먼트가 있어야 할 때 |
| `queryBy*` | Element \| null | No | 엘리먼트가 없을 수 있을 때 |
| `findBy*` | Promise&lt;Element&gt; | Yes | 비동기 엘리먼트 |

#### Available Queries

```typescript
// 텍스트로 찾기
screen.getByText('Hello');
screen.getByText(/hello/i);
screen.queryByText('Optional');
await screen.findByText('Loading...');

// ARIA role로 찾기
screen.getByRole('button');
screen.getByRole('heading');

// data-testid로 찾기
screen.getByTestId('submit-button');

// 라벨로 찾기 (aria-label 포함)
screen.getByLabelText('Username');
screen.getByLabelText('Close dialog'); // aria-label

// placeholder로 찾기
screen.getByPlaceholderText('Enter email');
```

#### Options

```typescript
// 부분 매칭
screen.getByText('Hello', { exact: false });

// 타임아웃 (findBy*)
await screen.findByText('Loaded', { timeout: 5000 });
```

### Matchers

#### DOM Presence

```typescript
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(element).toBeEmpty();
```

#### Attributes & Content

```typescript
expect(element).toHaveTextContent('Hello');
expect(element).toHaveTextContent(/hello/i);
expect(element).toHaveClass('active');
expect(element).toHaveClass('btn', 'primary');
expect(element).toHaveAttribute('disabled');
expect(element).toHaveAttribute('href', '/home');
```

#### Form Elements

```typescript
expect(input).toHaveValue('test@email.com');
expect(checkbox).toBeChecked();
expect(button).toBeDisabled();
expect(input).toHaveFocus();
```

#### Styles

```typescript
expect(element).toHaveStyle({ display: 'flex' });
expect(element).toHaveStyle({ color: 'red', fontSize: '16px' });
```

#### Hierarchy

```typescript
expect(parent).toContainElement(child);
expect(element).toContainHTML('<span>text</span>');
```

#### Negation

```typescript
expect(element).not.toBeInTheDocument();
expect(element).not.toBeVisible();
expect(button).not.toBeDisabled();
```

### Async Utilities

#### `waitFor(callback, options?)`
조건이 충족될 때까지 대기합니다.

```typescript
await waitFor(() => {
  expect(screen.getByText('Loaded')).toBeInTheDocument();
}, { timeout: 5000, interval: 100 });
```

#### `act(callback)`
React 상태 업데이트를 동기화합니다.

```typescript
await act(async () => {
  button.click();
});
```

### Basic Matchers

```typescript
expect(value).toBe(expected);
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(array).toContain(item);
expect(number).toBeGreaterThan(other);
expect(number).toBeLessThan(other);
```

## Migration Guide

### Legacy Mode (호환성)

기존 테스트에서 자동 렌더링을 사용했다면:

```typescript
import { setCompatMode, registerApp, render } from '@fix-my/test-runner';

// 레거시 모드 활성화
setCompatMode('auto');
registerApp(App);

// 인자 없이 render 호출
render();
```

### 권장 방식 (Manual Mode)

```typescript
import { render, screen } from '@fix-my/test-runner';

// RTL 스타일
render(<App />);
const button = screen.getByText('Click');
```

## Node.js VM 환경에서 사용

```typescript
import { runTestsInVM } from '@fix-my/test-runner';

const bundledCode = '...'; // 번들된 테스트 코드
const result = await runTestsInVM(bundledCode);

console.log(result.passed); // true/false
console.log(result.results); // 개별 테스트 결과
```

## 에러 메시지

쿼리 실패 시 유용한 에러 메시지를 제공합니다:

```
Unable to find element with text: "Missing Text"

<body>
<div class="container">
  <button>Click me</button>
</div>
</body>

Tip: Use findByText for async elements
```

## 브라우저 지원

- Chrome (최신)
- Firefox (최신)
- Safari (최신)
- Edge (최신)

**참고:** IE11은 지원하지 않습니다. React 18+ 필요.

## License

MIT