DorkOSDorkOS
Contributing

Testing

Testing patterns and conventions for DorkOS

Testing

DorkOS uses Vitest for all testing, with React Testing Library for component tests.

Running Tests

pnpm test

Runs all tests in watch mode.

pnpm test -- --run

Runs all tests once without watch mode. Useful for CI.

pnpm vitest run apps/server/src/services/__tests__/transcript-reader.test.ts

Runs a single test file directly.

Test File Structure

Tests live alongside source code in __tests__/ directories:

transcript-reader.test.ts
agent-manager.test.ts
session-broadcaster.test.ts
ChatPanel.test.tsx
index.ts

Component Tests

Component tests require the jsdom environment directive and a mock Transport.

Add the environment directive

Every component test file must start with the jsdom directive:

/**
 * @vitest-environment jsdom
 */

Set up the mock Transport

Use createMockTransport() from @dorkos/test-utils and wrap components in TransportProvider:

import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { TransportProvider } from '@/layers/shared/model';
import { createMockTransport } from '@dorkos/test-utils';

const mockTransport = createMockTransport();

function Wrapper({ children }: { children: React.ReactNode }) {
  return (
    <TransportProvider transport={mockTransport}>
      {children}
    </TransportProvider>
  );
}

Write your tests

describe('MyComponent', () => {
  it('renders expected content', () => {
    render(<MyComponent />, { wrapper: Wrapper });
    expect(screen.getByText('Expected')).toBeInTheDocument();
  });
});

Service Tests

Server tests mock Node.js modules like fs/promises:

import { describe, it, expect, vi } from 'vitest';

vi.mock('fs/promises');

describe('TranscriptReader', () => {
  it('returns session when found', async () => {
    vi.mocked(readFile).mockResolvedValue(Buffer.from(mockJsonl));
    const result = await transcriptReader.getSession('test-id');
    expect(result).toEqual(expect.objectContaining({ id: 'test-id' }));
  });
});

Hook Tests

import { renderHook, waitFor } from '@testing-library/react';

describe('useCustomHook', () => {
  it('returns expected state', async () => {
    const { result } = renderHook(() => useCustomHook(), {
      wrapper: Wrapper,
    });

    await waitFor(() => {
      expect(result.current.data).toBeDefined();
    });
  });
});

Key Conventions

Prop

Type

Mock Browser APIs

Components that use browser APIs like matchMedia need explicit mocks. Add these in beforeAll and clean up in afterEach.

beforeAll(() => {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: vi.fn().mockImplementation((query) => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: vi.fn(),
      removeListener: vi.fn(),
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      dispatchEvent: vi.fn(),
    })),
  });
});

Test Utilities

The @dorkos/test-utils package provides:

  • createMockTransport() — Creates a fully mocked Transport object with all 9 methods
  • Mock factories for sessions, messages, and other domain objects

Anti-Patterns

Avoid these common testing mistakes.

// NEVER test implementation details
expect(component.state.isOpen).toBe(true); // Wrong - test behavior instead

// NEVER use waitFor without an assertion
await waitFor(() => {}); // Wrong - always include an expect()

// NEVER leave console mocks without cleanup
vi.spyOn(console, 'error'); // Add mockRestore in afterEach

// NEVER use arbitrary timeouts
await new Promise((r) => setTimeout(r, 1000)); // Wrong - use waitFor

Next Steps