npm package: @scom/test
Description: Test runner for @scom packages
Latest version: 0.1.7

# @scom/test

A lightweight, fast test runner for @scom packages built with TypeScript and Node.js. Designed to be a simple alternative to Jest with zero external dependencies.

## Features

- 🚀 **Zero dependencies**: Uses only Node.js built-in modules (plus TypeScript and jsdom for execution)
- 📝 **TypeScript first**: Native TypeScript support with automatic transpilation
- 🧪 **Jest-compatible API**: Familiar testing syntax (`describe`, `it`, `expect`, etc.)
- ⚡ **Fast execution**: Lightweight runtime with minimal overhead
- 📊 **Clear reporting**: Detailed test results with timing and error information
- 🎯 **Focused testing**: Run specific files, directories, or patterns
- 🔧 **CLI and programmatic**: Use from command line or import as a module

## Installation

```bash
npm install @scom/test --save-dev
```

> **Note**: Use `npx @scom/test` to run the CLI - npm automatically resolves this to the correct binary.

## Quick Start

### 1. Write a test file

```typescript
// tests/math.test.ts
describe('Math utilities', () => {
  it('should add numbers correctly', () => {
    expect(1 + 1).toBe(2);
  });

  it('should handle async operations', async () => {
    const result = await Promise.resolve(42);
    expect(result).toBe(42);
  });
});
```

### 2. Run tests

```bash
# Run all tests
npx @scom/test

# Run specific test file
npx @scom/test tests/math.test.ts

# Run with verbose output
npx @scom/test --verbose
```

## Usage

### Command Line Interface

```bash
# Run all TypeScript tests in current directory
npx @scom/test

# Run tests in specific directory
npx @scom/test tests/

# Run specific test file
npx @scom/test tests/server.test.ts

# Run with options
npx @scom/test --verbose --timeout 30000 tests/

# Show help
npx @scom/test --help
```

#### Shell Compatibility

The `@scom/test` package uses npm's scoped package resolution, which automatically maps to the correct binary:

**Recommended usage:**
```bash
npx @scom/test --verbose tests/
```

**Alternative (via npm scripts):**
```bash
# In package.json
{
  "scripts": {
    "test": "@scom/test"
  }
}

# Then run:
npm test
npm run test -- --verbose  # Pass additional arguments
```

### Package.json Scripts

```json
{
  "scripts": {
    "test": "@scom/test",
    "test:verbose": "@scom/test --verbose"
  }
}
```

### Programmatic API

```typescript
import { runTestFile, findTestFiles, TestResult } from '@scom/test';

// Run a specific test file
const result: TestResult = await runTestFile('./tests/server.test.ts');
console.log(`Tests: ${result.passed}/${result.total}`);

// Find and run all test files
const testFiles = await findTestFiles('./tests');
for (const file of testFiles) {
  const result = await runTestFile(file);
  console.log(`${file}: ${result.passed}/${result.total} passed`);
}
```

## Test API Reference

### Test Structure

```typescript
describe('Test Suite', () => {
  // Setup and teardown
  beforeAll(async () => {
    // Run once before all tests in this suite
  });

  afterAll(async () => {
    // Run once after all tests in this suite
  });

  beforeEach(() => {
    // Run before each test
  });

  afterEach(() => {
    // Run after each test
  });

  // Test cases
  it('should do something', () => {
    expect(true).toBe(true);
  });

  test('alternative syntax for it', async () => {
    const result = await someAsyncFunction();
    expect(result).toEqual({ success: true });
  });

  // Nested test suites
  describe('Nested suite', () => {
    it('can be nested', () => {
      expect(1).toBeLessThan(2);
    });
  });
});
```

### Test Timeouts

Control test execution timeouts using `scomTest.setTimeout()`:

```typescript
// Set timeout for individual tests
it('should complete within custom timeout', async () => {
  scomTest.setTimeout(5000); // 5 seconds for this test only
  
  const result = await longRunningOperation();
  expect(result).toBeDefined();
});

// Set timeout in beforeEach for all tests in a suite
describe('Long running operations', () => {
  beforeEach(() => {
    scomTest.setTimeout(10000); // 10 seconds for all tests in this suite
  });

  it('should handle slow database queries', async () => {
    const data = await database.complexQuery();
    expect(data).toHaveLength(100);
  });

  it('should process large files', async () => {
    const result = await fileProcessor.processLargeFile();
    expect(result.success).toBe(true);
  });
});

// Set timeout for specific async operations
test('should timeout appropriately', async () => {
  // Default timeout applies
  await expect(quickOperation()).resolves.toBeDefined();
  
  // Custom timeout for slow operation
  scomTest.setTimeout(15000);
  await expect(verySlowOperation()).resolves.toBeDefined();
  
  // Reset to default timeout
  scomTest.setTimeout(30000); // or omit to use global default
});
```

#### Timeout Behavior

- **Default timeout**: 30 seconds (30000ms) for all tests
- **Global timeout**: Set via CLI `--timeout` option 
- **Test-specific timeout**: Use `scomTest.setTimeout()` within test functions
- **Suite-wide timeout**: Use `scomTest.setTimeout()` in `beforeEach` or `beforeAll`
- **Scope**: Timeout changes only affect the current test or suite
- **Reset**: Timeout automatically resets to default after each test

#### Best Practices

```typescript
describe('API Integration Tests', () => {
  // Set longer timeout for all integration tests
  beforeAll(() => {
    scomTest.setTimeout(60000); // 1 minute for integration tests
  });

  it('should authenticate user', async () => {
    // This test inherits the 60-second timeout
    const token = await authService.login(credentials);
    expect(token).toBeDefined();
  });

  it('should handle network delays', async () => {
    // Override with even longer timeout for this specific test
    scomTest.setTimeout(120000); // 2 minutes
    
    const response = await api.callSlowEndpoint();
    expect(response.status).toBe(200);
  });

  it('should use default timeout', async () => {
    // Back to suite default (60 seconds)
    const data = await api.getFastData();
    expect(data).toBeDefined();
  });
});
```

### Matchers

```typescript
// Equality
expect(value).toBe(expected);           // Strict equality (===)
expect(value).toEqual(expected);        // Deep equality
expect(value).not.toBe(expected);       // Negation

// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();

// Numbers
expect(number).toBeGreaterThan(3);
expect(number).toBeLessThan(10);
expect(number).toBeCloseTo(3.14, 2);    // Floating point comparison

// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');

// Arrays and Objects
expect(array).toContain(item);
expect(array).toHaveLength(3);

// Exceptions
expect(() => {
  throw new Error('test');
}).toThrow();
expect(() => {
  throw new Error('specific message');
}).toThrow('specific message');

// Async
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();
```

## CLI Options

| Option | Alias | Description | Default |
|--------|-------|-------------|---------|
| `--verbose` | `-v` | Show detailed output including passing tests | `false` |
| `--pattern` | `-p` | Test file pattern | `**/*.test.{ts,tsx}` |
| `--testNamePattern` | `-t` | Filter tests by test name pattern | - |
| `--timeout` | - | Test timeout in milliseconds | `30000` |
| `--bail` | `-b` | Stop on first test failure | `false` |
| `--help` | `-h` | Show help message | - |

## Configuration

The test runner currently uses default configuration. Configuration file support may be added in future versions.

## Migration from Jest

1. **Install @scom/test**:
   ```bash
   npm uninstall jest @types/jest
   npm install @scom/test --save-dev
   ```

2. **Update package.json**:
   ```json
   {
     "scripts": {
       "test": "@scom/test"
     }
   }
   ```

3. **Convert test files**: Ensure all test files use `.test.ts` extension

4. **Remove Jest config**: Delete `jest.config.js` and Jest configuration from `package.json`

5. **Update imports**: Most Jest APIs are compatible, but check for any Jest-specific utilities

## Examples

### Basic Test

```typescript
// tests/calculator.test.ts
class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
}

describe('Calculator', () => {
  let calc: Calculator;

  beforeEach(() => {
    calc = new Calculator();
  });

  it('should add two numbers', () => {
    expect(calc.add(2, 3)).toBe(5);
  });

  it('should handle negative numbers', () => {
    expect(calc.add(-1, 1)).toBe(0);
  });
});
```

### Async Test

```typescript
// tests/api.test.ts
describe('API calls', () => {
  it('should fetch data', async () => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    
    expect(response.status).toBe(200);
    expect(data).toHaveProperty('results');
  });

  it('should handle errors', async () => {
    await expect(
      fetch('https://api.example.com/invalid')
    ).rejects.toThrow();
  });

  it('should handle slow API calls', async () => {
    // Set custom timeout for slow API
    scomTest.setTimeout(60000); // 1 minute
    
    const response = await fetch('https://slow-api.example.com/data');
    expect(response.status).toBe(200);
  });
});
```

### Mock Functions

Basic mock functionality is available:

```typescript
// tests/service.test.ts
describe('UserService', () => {
  it('should call the API', () => {
    const mockFetch = scomTest.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve({ id: 1, name: 'John' })
    });
    
    global.fetch = mockFetch;
    
    // Test your service
    expect(mockFetch).toHaveBeenCalledWith('/api/users/1');
  });
});
```

## Troubleshooting

### Common Issues

1. **TypeScript compilation errors**: Ensure `tsconfig.json` is properly configured
2. **Module resolution**: Use relative imports or configure path mapping
3. **Async test timeouts**: Use `scomTest.setTimeout()` to increase timeout or CLI `--timeout` option
4. **File not found**: Check test file patterns and paths

### Debug Mode

Run with verbose output to see detailed information:

```bash
npx @scom/test --verbose tests/
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Add tests for your changes
4. Run the test suite: `npm test`
5. Submit a pull request