Testing & Quality

Testing Framework

Stacks uses Bun's test runner with additional utilities:

// tests/unit/CreatePostAction.test.ts
import { test, expect, describe, beforeEach } from 'bun:test'
import { createTestContext } from '@stacksjs/testing'
import CreatePostAction from '@/Actions/CreatePostAction'

describe('CreatePostAction', () => {
  let context: TestContext

  beforeEach(async () => {
    context = await createTestContext()
    await context.migrate()
  })

  test('creates a post with valid data', async () => {
    const user = await context.factory.user.create()

    const result = await context.actingAs(user).handle(CreatePostAction, {
      title: 'Test Post',
      content: 'This is test content',
    })

    expect(result.title).toBe('Test Post')
    expect(result.author_id).toBe(user.id)
  })

  test('fails without authentication', async () => {
    await expect(
      context.handle(CreatePostAction, { title: 'Test' })
    ).rejects.toThrow('Unauthorized')
  })

  test('validates required fields', async () => {
    const user = await context.factory.user.create()

    await expect(
      context.actingAs(user).handle(CreatePostAction, {})
    ).rejects.toThrow('title is required')
  })
})

Feature Tests (E2E)

// tests/feature/posts.test.ts
import { test, expect } from '@playwright/test'

test('user can create a post', async ({ page }) => {
  await page.goto('/login')
  await page.fill('[name="email"]', 'test@example.com')
  await page.fill('[name="password"]', 'password')
  await page.click('button[type="submit"]')

  await page.goto('/posts/create')
  await page.fill('[name="title"]', 'My New Post')
  await page.fill('[name="content"]', 'Post content here')
  await page.click('button[type="submit"]')

  await expect(page).toHaveURL(/\/posts\/\d+/)
  await expect(page.locator('h1')).toContainText('My New Post')
})

Linting & Code Quality

Stacks includes preconfigured code quality tools:

# Run all linters
buddy lint

# Auto-fix issues
buddy lint:fix

# Type checking
buddy typecheck

# Format code
buddy format

Configuration

// config/lint.ts
export default {
  rules: {
    'no-console': 'warn',
    'no-unused-vars': 'error',
    '@typescript-eslint/explicit-function-return-type': 'off',
  },

  ignore: [
    'storage/framework/**',
    'node_modules/**',
  ],
}

CI/CD Integration

GitHub Actions workflow (auto-generated):

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1

      - run: bun install
      - run: bun run lint
      - run: bun run typecheck
      - run: bun run test
      - run: bun run build

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1

      - run: bun install
      - run: bun run deploy:prod