โš ๏ธEarly Alpha โ€” Org-press is experimental. Perfect for hackers and tinkerers, not ready for production. Documentation may be incomplete or inaccurate.

Overview

The Test plugin enables literate testing directly within your org-mode documents using Vitest. Write tests alongside your code, run them from the CLI, and see results rendered inline in your documents.

Features

  • ๐Ÿงช Literate Testing - Write tests in the same document as your code
  • โšก Vitest Powered - Fast, modern test runner with excellent DX
  • ๐Ÿ“Š Inline Results - Test results render directly in your documents
  • ๐Ÿ” Coverage Support - Optional code coverage reporting
  • ๐Ÿ‘€ Watch Mode - Re-run tests automatically on changes
  • ๐ŸŽฏ Filtering - Run tests by file or block name

Installation

npm install @org-press/block-test vitest

Register in .org-press/config.ts:

import { testPlugin } from '@org-press/block-test';

export default {
  plugins: [testPlugin]
};

Basic Usage

Simple Example

Write tests directly in your org file:

describe('Math Operations', () => {
  it('adds numbers correctly', () => {
    expect(1 + 2).toBe(3);
  });

  it('multiplies numbers correctly', () => {
    expect(3 * 4).toBe(12);
  });
});

Syntax

#+NAME: my-tests
#+begin_src typescript :use test
import { describe, it, expect } from 'vitest';

describe('feature', () => {
  it('works correctly', () => {
    expect(true).toBe(true);
  });
});
#+end_src

Parameters

ParameterRequiredDescriptionExample
:use testYesActivates test block processing:use test
:nameNoBlock name for filtering:name my-tests
:coverageNoEnable coverage display:coverage

CLI Commands

Running All Tests

orgp test

Running Tests in a Specific File

orgp test content/math.org

Filtering by Block Name

orgp test --name math-tests

Watch Mode

orgp test --watch

With Coverage

orgp test --coverage

Hashbang Support

Make your org file executable:

#!/usr/bin/env orgp
#+TITLE: My Tests

#+NAME: tests
#+begin_src typescript :use test
describe('tests', () => {
  it('works', () => expect(1).toBe(1));
});
#+end_src

Then run directly:

chmod +x my-tests.org
./my-tests.org test

Testing Patterns

Testing Functions from Other Blocks

Import functions from other code blocks in your org files:

// In math.org - define the function
#+NAME: add-function
#+begin_src typescript :use sourceOnly
export function add(a: number, b: number): number {
  return a + b;
}
#+end_src

// In the same or another file - test it
#+NAME: add-tests
#+begin_src typescript :use test
import { add } from './math.org?name=add-function';

describe('add', () => {
  it('adds positive numbers', () => {
    expect(add(1, 2)).toBe(3);
  });

  it('adds negative numbers', () => {
    expect(add(-1, -2)).toBe(-3);
  });

  it('handles zero', () => {
    expect(add(0, 5)).toBe(5);
  });
});
#+end_src

Async Tests

describe('Async Operations', () => {
  it('resolves promises', async () => {
    const result = await Promise.resolve(42);
    expect(result).toBe(42);
  });

  it('handles async/await', async () => {
    const fetchData = async () => ({ value: 'test' });
    const data = await fetchData();
    expect(data.value).toBe('test');
  });
});

Mocking

import { vi } from 'vitest';

describe('Mocking', () => {
  it('mocks functions', () => {
    const mockFn = vi.fn().mockReturnValue(42);
    expect(mockFn()).toBe(42);
    expect(mockFn).toHaveBeenCalled();
  });

  it('spies on methods', () => {
    const obj = {
      method: () => 'original'
    };
    const spy = vi.spyOn(obj, 'method').mockReturnValue('mocked');
    expect(obj.method()).toBe('mocked');
    spy.mockRestore();
  });
});

Setup and Teardown

describe('With Setup/Teardown', () => {
  let testData: string[];

  beforeEach(() => {
    testData = ['a', 'b', 'c'];
  });

  afterEach(() => {
    testData = [];
  });

  it('has initial data', () => {
    expect(testData).toHaveLength(3);
  });

  it('can modify data', () => {
    testData.push('d');
    expect(testData).toHaveLength(4);
  });
});

Inline Results Display

When viewing your org file in the browser during development, test blocks show results inline:

Result Components

  • Summary Bar - Shows pass/fail/skip counts with color coding
  • Test List - Individual test results with expand/collapse
  • Error Details - Stack traces and diff output for failures
  • Coverage Bars - Visual coverage metrics (when enabled)

Result States

StateColorDescription
PassGreenAll tests passed
FailRedOne or more tests failed
PendingYellowTests not yet run
SkipBlueTests marked as skipped

Project Setup Example

Directory Structure

my-project/
โ”œโ”€โ”€ .org-press/
โ”‚   โ””โ”€โ”€ config.ts        # Plugin registration
โ”œโ”€โ”€ content/
โ”‚   โ”œโ”€โ”€ index.org        # Main content
โ”‚   โ”œโ”€โ”€ math.org         # Math functions + tests
โ”‚   โ””โ”€โ”€ utils.org        # Utility functions + tests
โ””โ”€โ”€ package.json

Config File

// .org-press/config.ts
import { testPlugin } from '@org-press/block-test';

export default {
  contentDir: 'content',
  plugins: [testPlugin],
};

Package Scripts

{
  "scripts": {
    "dev": "orgp dev",
    "build": "orgp build",
    "test": "orgp test",
    "test:watch": "orgp test --watch",
    "test:coverage": "orgp test --coverage"
  }
}

Tips and Best Practices

Organization

  • Group related tests in the same org file as the code they test
  • Use descriptive #+NAME: blocks for easy filtering
  • Keep test blocks close to the code they test

Performance

  • Use --watch during development for fast feedback
  • Filter by file or name when working on specific features
  • Run full test suite before committing

Debugging

  • Use console.log() in tests - output appears in CLI
  • Check the generated test files in node_modules/.cache/org-test/
  • Run with --reporter=verbose for detailed output

Coverage

  • Enable coverage to find untested code paths
  • Aim for meaningful coverage, not 100%
  • Focus coverage on critical business logic

API Reference

Plugin Configuration

interface TestPlugin extends BlockPlugin {
  name: 'test';
  defaultExtension: 'js';

  // Match blocks with :use test
  matches: (block: CodeBlock) => boolean;

  // Transform block for rendering
  transform: (block: CodeBlock, context: TransformContext) => Promise<TransformResult>;

  // CLI command registration
  cli: {
    command: 'test';
    description: 'Run test blocks in org files';
    execute: (args: string[], context: CliContext) => Promise<number>;
  };
}

Test Result Types

interface TestResult {
  name: string;
  status: 'pass' | 'fail' | 'skip';
  duration: number;
  error?: {
    message: string;
    expected?: unknown;
    actual?: unknown;
    stack?: string;
  };
}

interface TestBlockResult {
  blockId: string;
  blockName?: string;
  tests: TestResult[];
  coverage?: CoverageData;
  duration: number;
}

Troubleshooting

Tests Not Found

  • Verify :use test parameter is present on the block
  • Check that the block language is TypeScript or JavaScript
  • Ensure the content directory is correctly configured

Import Errors

  • Use relative paths: ./file.org?name=block-name
  • Verify the target block exists and exports correctly
  • Check that Vitest can resolve the imports

CLI Not Working

  • Ensure @org-press/block-test is installed
  • Verify the plugin is registered in config
  • Check that orgp is available in PATH

Results Not Displaying

  • Run orgp test to generate results first
  • Check browser console for errors
  • Verify the wrapper component is loading

See Also