How to Write Unit and Integration Tests for the i-have-adhd Extension

The i-have-adhd repository ships a complete test suite that demonstrates both unit-style tests for individual components and integration-style smoke tests that exercise the extension through the Pi and OMP runtimes.

The ayghri/i-have-adhd repository provides a fully functional harness to write unit or integration tests for the i-have-adhd extension. By mirroring the existing patterns in tests/ and scripts/, you can validate everything from front-matter stripping to full command-toggle workflows in isolated agent environments.

Unit Test Architecture and Key Files

The unit test layer focuses on pure functions and small behaviors without spawning a full runtime. The extension entry point in extensions/i-have-adhd.ts exports stripFrontmatter for front-matter stripping, while extensions/context-compat.ts provides contextMessages and latestMarkerIsActive for state inspection.

The primary unit-level test files are:

These files use standard unittest.TestCase classes and import utilities directly from the extensions/ package using the repository’s module path structure.

Writing a New Unit Test

When you add a pure-function change, create a new unittest.TestCase in the tests/ directory and import the target function from its module path. For example, stripFrontmatter as implemented in extensions/i-have-adhd.ts can be tested like this:

import unittest
from extensions.i_have_adhd import stripFrontmatter  # type: ignore

class FrontMatterTest(unittest.TestCase):
    def test_strips_simple_frontmatter(self):
        src = "---\nname: demo\n---\nHello world"
        self.assertEqual(stripFrontmatter(src), "Hello world")

This pattern follows the existing hook tests in tests/test_always_on_hooks.py, which rely on repository-relative imports and runtime-agnostic helpers such as run_hook and run_codex_hook to execute scripts in isolation.

Integration Test Architecture

Integration tests spin up a temporary Pi or OMP agent, load the extension, issue commands, and assert on the resulting session state. The main driver is scripts/check_pi_extension.py, which defines the RpcClient class and provides verification helpers like message_count, latest_enabled, and status_texts.

The integration layer validates end-to-end behaviors such as:

  • Command registration, confirming that /i-have-adhd appears in the available command list.
  • Toggling ADHD mode on and off via RPC prompts.
  • Verifying that UI status texts and session entry markers update correctly after each command.

Writing a New Integration Smoke Test

To cover end-to-end behavior, extend the pattern used in scripts/check_pi_extension.py. The following skeleton builds an isolated environment, instantiates an RpcClient, and asserts that toggling the extension off clears the UI status and adds the disabled marker:

import unittest
from pathlib import Path
from scripts.check_pi_extension import RpcClient, build_isolated_env, status_texts, message_count

class IHaveADHDSmokeTest(unittest.TestCase):
    def test_toggle_off(self):
        from scripts.check_pi_extension import ROOT
        env = build_isolated_env(agent_dir="tmp-agent")
        client = RpcClient(
            "pi",
            env,
            "--no-session",
            "-e",
            str(ROOT / "extensions/i-have-adhd.ts")
        )

        try:
            cmds, _ = client.request("commands", {"type": "get_commands"})
            self.assertIn("i-have-adhd", {c["name"] for c in cmds["data"]["commands"]})

            client.request("toggle-on", {"type": "prompt", "message": "/i-have-adhd"})
            off_evt = client.request("toggle-off", {"type": "prompt", "message": "/i-have-adhd"})[1]
            self.assertIn(None, status_texts(off_evt))

            entries, _ = client.request("entries", {"type": "get_entries"})
            self.assertEqual(message_count(entries, "i-have-adhd-disabled"), 1)
        finally:
            client.close()

Key steps in this flow:

  1. Isolate the environmentbuild_isolated_env creates a temporary agent directory.
  2. Instantiate the clientRpcClient launches the Pi or OMP runtime with the extension entry point loaded.
  3. Verify registration — Query the command list to ensure i-have-adhd is present.
  4. Drive the toggle — Send prompt messages via RPC and inspect UI events using status_texts.
  5. Assert on session state — Fetch entries and use message_count to confirm markers such as i-have-adhd-disabled appear exactly once.

Testing the Always-On Hook Scripts

The always-on hook scripts inject the ADHD banner when an opt-in flag is detected. The file tests/test_always_on_hooks.py already verifies that these scripts correctly strip front-matter and respect the opt-in flag across supported runtimes.

To add coverage for a new runtime, extend the test class with a new case that invokes your hook via the existing run_hook helper:

def test_new_runtime_respects_opt_in(self):
    command = ["node", self.plugin_root / "hooks" / "custom-runtime.mjs"]
    result = self.run_hook(command)
    self.assertEqual(0, result.returncode)

This approach keeps hook tests runtime-agnostic by relying on standard subprocess execution and output assertions.

Running the Tests Locally

Execute the unit tests using Python’s built-in discovery:

python3 -m unittest discover -s tests -v

Run the integration smoke tests directly from the driver script for either runtime:

python3 scripts/check_pi_extension.py --runtime pi
python3 scripts/check_pi_extension.py --runtime omp

Both commands print a confirmation message when the extension behaves as expected. New test files added under tests/ are picked up automatically as long as they follow the test_*.py naming convention.

Summary

  • The ayghri/i-have-adhd repository separates unit tests for pure functions and hooks from integration smoke tests that drive the full Pi or OMP runtime.
  • Write unit tests by importing functions such as stripFrontmatter from extensions.i_have_adhd into standard unittest classes.
  • Write integration tests by reusing RpcClient, build_isolated_env, status_texts, and message_count from scripts/check_pi_extension.py.
  • Validate always-on hooks with the run_hook helper in tests/test_always_on_hooks.py to assert correct front-matter handling and opt-in behavior.
  • Run the full suite with python3 -m unittest discover -s tests -v and the RPC driver with python3 scripts/check_pi_extension.py.

Frequently Asked Questions

How do I write unit tests for the i-have-adhd extension?

Create a Python file under tests/ that imports the function you want to exercise from the extensions/ package. Use the standard unittest library and assert on return values from pure functions like stripFrontmatter, following the pattern established in tests/test_always_on_hooks.py.

How do I write integration tests for the i-have-adhd extension?

Use the RpcClient class defined in scripts/check_pi_extension.py to spin up a temporary Pi or OMP agent with the extension loaded. Send RPC commands to trigger /i-have-adhd, then inspect the returned events with helpers like status_texts and message_count to verify session state changes.

What is the fastest way to run the i-have-adhd test suite?

Run python3 -m unittest discover -s tests -v for unit tests, or execute python3 scripts/check_pi_extension.py --runtime pi (or --runtime omp) for integration smoke tests. Both commands report pass or fail status directly in the terminal.

Where are the main source files for the i-have-adhd extension?

The entry point is extensions/i-have-adhd.ts, which registers commands and manages rule injection. Context utilities such as contextMessages and latestMarkerIsActive live in extensions/context-compat.ts, and both are exercised by the existing unit and integration tests according to the ayghri/i-have-adhd source code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →