# Testing
Source: https://dorkos.ai/docs/contributing/testing

Testing patterns and conventions for DorkOS



{/* Internal deep-dive (repo-only): contributing/browser-testing.md - keep in sync */}

# Testing [#testing]

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

## Running Tests [#running-tests]

<Tabs items="['All tests', 'Single run', 'Specific file']">
  <Tab value="All tests">
    `bash pnpm test `

     Runs all tests in watch mode.
  </Tab>

  <Tab value="Single run">
    `bash pnpm test -- --run `

     Runs all tests once. Use this for CI.
  </Tab>

  <Tab value="Specific file">
    `bash pnpm vitest run apps/server/src/services/session/__tests__/transcript-reader.test.ts `
  </Tab>
</Tabs>

## Test File Structure [#test-file-structure]

Tests live alongside source code in `__tests__/` directories:

<Files>
  <Folder name="apps">
    <Folder name="server/src">
      <Folder name="services/session">
        <Folder name="__tests__">
          <File name="transcript-reader.test.ts" />

          <File name="session-list-broadcaster.test.ts" />
        </Folder>
      </Folder>
    </Folder>

    <Folder name="client/src/layers">
      <Folder name="features/chat">
        <Folder name="__tests__">
          <File name="ChatPanel.test.tsx" />
        </Folder>
      </Folder>
    </Folder>
  </Folder>

  <Folder name="packages">
    <Folder name="test-utils">
      <File name="index.ts" />
    </Folder>
  </Folder>
</Files>

## Component Tests [#component-tests]

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

<Steps>
  <Step>
    ### Add the environment directive [#add-the-environment-directive]

    Every component test file must start with:

    ```typescript
    /**
     * @vitest-environment jsdom
     */
    ```
  </Step>

  <Step>
    ### Set up the mock Transport [#set-up-the-mock-transport]

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

    ```typescript
    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>
      );
    }
    ```
  </Step>

  <Step>
    ### Write your tests [#write-your-tests]

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

## Service Tests [#service-tests]

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

```typescript
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 [#hook-tests]

```typescript
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 [#key-conventions]

<TypeTable
  type="{
  '@vitest-environment jsdom': {
    type: 'directive',
    description: 'Required at the top of every component test file',
  },
  'createMockTransport()': {
    type: 'function',
    description: 'Creates a fully mocked Transport from @dorkos/test-utils',
  },
  'TransportProvider wrapper': {
    type: 'pattern',
    description: 'Wrap components in TransportProvider with a mock Transport',
  },
  '@testing-library/jest-dom': {
    type: 'import',
    description: 'DOM assertion matchers like toBeInTheDocument()',
  },
  'matchMedia mock': {
    type: 'pattern',
    description: 'Mock window.matchMedia in beforeAll for responsive components',
  },
}"
/>

## Mock Browser APIs [#mock-browser-apis]

<Callout type="warn">
  Components that use browser APIs like `matchMedia` need explicit mocks. Add them in `beforeAll`
  and clean up in `afterEach`.
</Callout>

```typescript
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 [#test-utilities]

The `@dorkos/test-utils` package provides:

* `createMockTransport()`: fully mocked Transport with all required methods
* `FakeAgentRuntime`: full `AgentRuntime` implementation with `vi.fn()` spies and scenario queue
* `collectDurableEvents(app, sessionId, opts?)`: opens the durable `GET /api/sessions/:id/events` stream against a real listening server and collects parsed SSE frames
* `TestScenario`: named scenario keys (`SimpleText`, `ToolCall`, `TodoWrite`, `Error`)
* `testScenarios`: scenario builders that produce `StreamEvent` sequences
* Mock factories for sessions, messages, and other domain objects

### FakeAgentRuntime (Server Route Tests) [#fakeagentruntime-server-route-tests]

Server tests that need an `AgentRuntime` should use `FakeAgentRuntime` instead of hand-rolling mock objects:

```typescript
import { FakeAgentRuntime, TestScenario, testScenarios } from '@dorkos/test-utils';

let fakeRuntime: FakeAgentRuntime;

beforeEach(() => {
  fakeRuntime = new FakeAgentRuntime();
  fakeRuntime.withScenarios([testScenarios[TestScenario.SimpleText]('Hello')]);
});
```

`FakeAgentRuntime` implements every method on the `AgentRuntime` interface. If the interface changes, tests will fail to compile.

## Anti-Patterns [#anti-patterns]

<Callout type="error">
  Avoid these common testing mistakes.
</Callout>

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

// NEVER use waitFor without an assertion
await waitFor(() => {}); // 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)); // use waitFor
```

## Next Steps [#next-steps]

<Cards>
  <Card title="Architecture" href="/docs/contributing/architecture">
    Understand the Transport interface and how mock Transports fit in
  </Card>

  <Card title="Development Setup" href="/docs/contributing/development-setup">
    Get your local environment configured for running tests
  </Card>

  <Card title="API Reference" href="/docs/api">
    Explore the endpoints your tests may need to mock
  </Card>
</Cards>
