Skip to content

Testing

Fast loop: npx blipkit validate after every meaningful change. It catches schema violations (a field with the wrong type, a missing required field, a link that isn’t http or https) in milliseconds, without the app running. It checks shape, not limits: extra actions or facts aren’t flagged (the runtime cuts them later), and a date field fed a relative number is still a valid date, so Builders: one per layout’s gotcha is yours to catch.

Unit-test the pure logic. onEvent/onAction aren’t covered by validate, and neither is anything conditional on preferences or prior state. The pattern Blipbar’s own extensions use: keep your actual decision logic (what state a session should move to given an event, what a webhook body means) in plain, exported functions that take data in and return data out, with no ctx, and re-export them below your defineExtension call for tests to import directly:

export default defineExtension({ /* … */ });
// Re-exported so tests can exercise the pure pieces directly without going through
// defineExtension's Context-shaped surface.
export { applyEvent, sweepStaleness } from "./state-machine";

Then test with Node’s built-in runner (no extra dependency):

src/test/state-machine.test.ts
import assert from "node:assert/strict";
import { test } from "node:test";
import { applyEvent } from "../state-machine";
test("a permission-request event moves a session to attention", () => {
const next = applyEvent(undefined, fixtureEvent, "2026-09-25T00:00:00Z", "reply-123");
assert.equal(next.state, "attention");
});

A setup that scales: fixture JSON files of real payloads beside the tests, a mock Context for driving onAction end to end, and a test script that compiles the tests with tsc into a folder of their own and runs node --test on the output.

Before you build against a mock, check the real shape once. If you’re integrating with an external hook system or API, log the raw payload (ctx.log.debug(JSON.stringify(event.body))) the first time it fires for real and compare it against whatever you assumed. Field names and nesting from a vendor’s docs are exactly the kind of thing that drifts.