# Jest to Vitest Migration Patterns

## Overview

This reference provides syntax mappings and patterns for migrating tests from Jest (with Spectator) to Vitest (with Angular Testing Library).

## Configuration Migration

### Jest Config → Vitest Config

**Before (jest.config.ts):**
```typescript
export default {
  displayName: 'my-lib',
  preset: '../../jest.preset.js',
  setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
  coverageDirectory: '../../coverage/libs/my-lib',
  transform: {
    '^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular',
  },
  transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
  snapshotSerializers: [
    'jest-preset-angular/build/serializers/no-ng-attributes',
    'jest-preset-angular/build/serializers/ng-snapshot',
  ],
};
```

**After (vitest.config.ts):**
```typescript
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular';

export default defineConfig({
  plugins: [angular()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['src/test-setup.ts'],
    include: ['**/*.spec.ts'],
    reporters: ['default'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
    },
  },
});
```

### Test Setup Migration

**Before (test-setup.ts - Jest):**
```typescript
import 'jest-preset-angular/setup-jest';
```

**After (test-setup.ts - Vitest):**
```typescript
import '@analogjs/vitest-angular/setup-zone';
```

## Import Changes

### Test Function Imports

**Before (Jest):**
```typescript
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
```

**After (Vitest):**
```typescript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
```

### Mock Imports

**Before (Jest):**
```typescript
jest.fn()
jest.spyOn()
jest.mock()
jest.useFakeTimers()
```

**After (Vitest):**
```typescript
vi.fn()
vi.spyOn()
vi.mock()
vi.useFakeTimers()
```

## Mock Migration Patterns

### Function Mocks

**Before (Jest):**
```typescript
const mockFn = jest.fn();
const mockFnWithReturn = jest.fn().mockReturnValue('value');
const mockFnWithAsync = jest.fn().mockResolvedValue('async value');
```

**After (Vitest):**
```typescript
const mockFn = vi.fn();
const mockFnWithReturn = vi.fn().mockReturnValue('value');
const mockFnWithAsync = vi.fn().mockResolvedValue('async value');
```

### Spy Migration

**Before (Jest):**
```typescript
const spy = jest.spyOn(service, 'method');
spy.mockImplementation(() => 'mocked');
```

**After (Vitest):**
```typescript
const spy = vi.spyOn(service, 'method');
spy.mockImplementation(() => 'mocked');
```

### Module Mocks

**Before (Jest):**
```typescript
jest.mock('@isa/core/logging', () => ({
  logger: jest.fn(() => ({
    info: jest.fn(),
    error: jest.fn(),
  })),
}));
```

**After (Vitest):**
```typescript
vi.mock('@isa/core/logging', () => ({
  logger: vi.fn(() => ({
    info: vi.fn(),
    error: vi.fn(),
  })),
}));
```

## Spectator → Angular Testing Library

### Component Testing

**Before (Spectator):**
```typescript
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';

describe('MyComponent', () => {
  let spectator: Spectator<MyComponent>;
  const createComponent = createComponentFactory({
    component: MyComponent,
    imports: [CommonModule],
    providers: [
      { provide: MyService, useValue: mockService },
    ],
  });

  beforeEach(() => {
    spectator = createComponent();
  });

  it('should render title', () => {
    expect(spectator.query('.title')).toHaveText('Hello');
  });

  it('should handle click', () => {
    spectator.click('.button');
    expect(mockService.doSomething).toHaveBeenCalled();
  });
});
```

**After (Angular Testing Library):**
```typescript
import { render, screen, fireEvent } from '@testing-library/angular';

describe('MyComponent', () => {
  it('should render title', async () => {
    await render(MyComponent, {
      imports: [CommonModule],
      providers: [
        { provide: MyService, useValue: mockService },
      ],
    });

    expect(screen.getByText('Hello')).toBeInTheDocument();
  });

  it('should handle click', async () => {
    await render(MyComponent, {
      providers: [{ provide: MyService, useValue: mockService }],
    });

    fireEvent.click(screen.getByRole('button'));
    expect(mockService.doSomething).toHaveBeenCalled();
  });
});
```

### Query Selectors

| Spectator | Angular Testing Library |
|-----------|------------------------|
| `spectator.query('.class')` | `screen.getByTestId()` or `screen.getByRole()` |
| `spectator.queryAll('.class')` | `screen.getAllByRole()` |
| `spectator.query('button')` | `screen.getByRole('button')` |
| `spectator.query('[data-testid]')` | `screen.getByTestId()` |

### Events

| Spectator | Angular Testing Library |
|-----------|------------------------|
| `spectator.click(element)` | `fireEvent.click(element)` or `await userEvent.click(element)` |
| `spectator.typeInElement(value, element)` | `await userEvent.type(element, value)` |
| `spectator.blur(element)` | `fireEvent.blur(element)` |
| `spectator.focus(element)` | `fireEvent.focus(element)` |

### Service Testing

**Before (Spectator):**
```typescript
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';

describe('MyService', () => {
  let spectator: SpectatorService<MyService>;
  const createService = createServiceFactory({
    service: MyService,
    providers: [
      { provide: HttpClient, useValue: mockHttp },
    ],
  });

  beforeEach(() => {
    spectator = createService();
  });

  it('should fetch data', () => {
    spectator.service.getData().subscribe(data => {
      expect(data).toEqual(expectedData);
    });
  });
});
```

**After (TestBed):**
```typescript
import { TestBed } from '@angular/core/testing';

describe('MyService', () => {
  let service: MyService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        MyService,
        { provide: HttpClient, useValue: mockHttp },
      ],
    });
    service = TestBed.inject(MyService);
  });

  it('should fetch data', () => {
    service.getData().subscribe(data => {
      expect(data).toEqual(expectedData);
    });
  });
});
```

## Async Testing

### Observable Testing

**Before (Jest):**
```typescript
it('should emit values', (done) => {
  service.data$.subscribe({
    next: (value) => {
      expect(value).toBe(expected);
      done();
    },
  });
});
```

**After (Vitest):**
```typescript
import { firstValueFrom } from 'rxjs';

it('should emit values', async () => {
  const value = await firstValueFrom(service.data$);
  expect(value).toBe(expected);
});
```

### Timer Mocks

**Before (Jest):**
```typescript
jest.useFakeTimers();
service.startTimer();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
jest.useRealTimers();
```

**After (Vitest):**
```typescript
vi.useFakeTimers();
service.startTimer();
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
vi.useRealTimers();
```

## Common Matchers

| Jest | Vitest |
|------|--------|
| `expect(x).toBe(y)` | Same |
| `expect(x).toEqual(y)` | Same |
| `expect(x).toHaveBeenCalled()` | Same |
| `expect(x).toHaveBeenCalledWith(y)` | Same |
| `expect(x).toMatchSnapshot()` | `expect(x).toMatchSnapshot()` |
| `expect(x).toHaveText('text')` | `expect(x).toHaveTextContent('text')` (with jest-dom) |

## Migration Checklist

1. [ ] Update `vitest.config.ts`
2. [ ] Update `test-setup.ts`
3. [ ] Replace `jest.fn()` with `vi.fn()`
4. [ ] Replace `jest.spyOn()` with `vi.spyOn()`
5. [ ] Replace `jest.mock()` with `vi.mock()`
6. [ ] Replace Spectator with Angular Testing Library
7. [ ] Update queries to use accessible selectors
8. [ ] Update async patterns
9. [ ] Run tests and fix any remaining issues
10. [ ] Remove Jest dependencies from `package.json`
