skip to the page

@jam-overture/loom/testing

Fixtures and doubles: the sample trees, clocks and scripted interpreters Loom tests itself with.

Everything below is exported from that import. The names, the signatures and the sentences are read from the package itself rather than written here, so this page says what the copy of Loom in your node_modules says — and it changes in the same pull request the code does.

54 exports, in 7 modules. Generated from ./dist/testing/index.d.ts, which is the declaration file this package publishes for @jam-overture/loom/testing.

Install this first. @jam-overture/loom/testing loads it the moment the import runs. Without it, the import itself fails — before any of your own code has run.

  • react^19.0.0optional peer dependency
pnpm add react

No import here has everything behind it. @jam-overture/loom/testing publishes 54 of the 1,298 names this package publishes. The other 1,244 are behind one of the 16 other imports, and not one of those imports publishes a single name this one does. The imports do not nest: a name that is not on this page is not a name that does not exist, and the search at the top of the page says which import it comes from.

Definitions

testing/definitions

The primitives of sampleTree, declared through the §4 contract, so registry and renderer tests run against real declarations rather than a stand-in for them. testPrimitives in primitives.ts is the same component set without declarations, which is what §3's tests need.

pageDefinitionvalue

const pageDefinition: PrimitiveEntry

cardDefinitionvalue

const cardDefinition: PrimitiveEntry

headerDefinitionvalue

const headerDefinition: PrimitiveEntry

footerDefinitionvalue

const footerDefinition: PrimitiveEntry

testDefinitionsvalue

const testDefinitions: readonly PrimitiveEntry[]

registryOffunction

Registration is fallible, and a test that wanted a registry has nothing to say about a failure to build one, so this unwraps loudly rather than making every caller narrow a Result it does not care about.

const registryOf: (entries: readonly PrimitiveEntry[]) => PrimitiveRegistry

testRegistryfunction

const testRegistry: () => PrimitiveRegistry

Doubles

testing/doubles

Test doubles for the impure seams this file owns — the interpreter and its repairer, the clock, and the event sink. The runtime's other two seams are doubled where they are defined: sequentialIdFactory in ids.ts and fixedPolicy in runtime/policy-source.ts.

FIXED_INSTANTvalue

const FIXED_INSTANT = "2026-07-28T00:00:00.000Z"

fixedClockfunction

const fixedClock: (instant?: string) => Clock

CollectingEventSinktype

type CollectingEventSink = EventSink & {
    readonly envelopes: readonly RuntimeEventEnvelope[];
    readonly types: () => readonly string[];
};

collectingEventSinkfunction

const collectingEventSink: () => CollectingEventSink

failingEventSinkfunction

A sink that fails on the events it was told to fail on and collects the rest, so a test can assert both halves at once: that the change survived, and that narration carried on past the failure rather than stopping at it.

const failingEventSink: (failOn: readonly string[], mode?: "throw" | "reject") => CollectingEventSink

RecordingModelClienttype

type RecordingModelClient = ModelClient & {
    readonly requests: readonly ModelRequest[];
};

scriptedModelClientfunction

A model that answers from a script and remembers what it was asked, so the interpreter's request assembly and its reply handling can be tested separately, and neither needs a network or a key.

const scriptedModelClient: (reply: string | Result<ModelCompletion, ModelClientError>, servedBy?: string) => RecordingModelClient

HangingModelClienttype

type HangingModelClient = ModelClient & {
    /** Whether the runtime's ceiling aborted the call, and what it said. */
    readonly abortedWith: () => string | undefined;
};

hangingModelClientfunction

A model that never answers — the failure mode a try/catch cannot see and the reason modelInterpreter has a ceiling.

Shown in use on When nothing comes back — Seeing it in your own app

const hangingModelClient: () => HangingModelClient

RecordingInterpretertype

type RecordingInterpreter = ChangeInterpreter & {
    readonly intents: readonly EditIntent[];
};

scriptedInterpreterfunction

Returns whatever it was handed, so tests drive the pipeline from a fixed script, and remembers what it was asked — which is how a test asserts that a write refused before interpretation never spent a model call.

const scriptedInterpreter: (script: Result<ProposedChange, InterpretationError>) => RecordingInterpreter

RecordingRepairertype

type RecordingRepairer = ChangeRepairer & {
    readonly requests: readonly RepairRequest[];
};

scriptedRepairerfunction

A repairer that answers from a script and remembers what it was asked, so a test can assert both that the refusal reached it and that exactly one attempt was made.

const scriptedRepairer: (script: Result<ProposedChange, InterpretationError>) => RecordingRepairer

IntentDrafttype

type IntentDraft = {
    readonly treeId: TreeId;
    readonly baseRevision: number;
    readonly utterance?: string;
    readonly origin?: IntentOrigin;
    readonly actor?: string;
};

buildIntentfunction

const buildIntent: (idFactory: IdFactory, draft: IntentDraft) => EditIntent

ProposalDrafttype

type ProposalDraft = {
    readonly intentId: IntentId;
    readonly delta: TreeDelta;
    readonly rationale?: string;
    readonly origin?: IntentOrigin;
    readonly actor?: string;
    readonly authoredBy?: AuthorKind;
    readonly confidence?: number;
};

buildProposalfunction

const buildProposal: (idFactory: IdFactory, draft: ProposalDraft) => ProposedChange

HangingSeamtype

What a hanging seam double reports, beside the entry a registry takes.

type HangingSeam<TEntry> = {
    readonly entry: TEntry;
    /** Whether the runtime's ceiling aborted the call, and what it said. */
    readonly abortedWith: () => string | undefined;
};

hangingSourcefunction

A source that never answers — the sibling of hangingModelClient at the data seam, published for the same reason.

const hangingSource: (id: string, description: string) => HangingSeam<SourceEntry>

hangingEndpointfunction

An endpoint that never says where a form posts — the same double at the submission seam.

const hangingEndpoint: (id: string, description: string) => HangingSeam<EndpointEntry>

NOTHING_MEASUREDvalue

Every fact an analysis states, measuring nothing.

const NOTHING_MEASURED: ChangeAnalysis

AssessmentDrafttype

The facts a test wants to be true of a judged change. Everything else is absent, empty or zero.

type AssessmentDraft = {
    /** The change being judged. Its delta decides what the inverse is written against. */
    readonly proposal: ProposedChange;
    /** What the delta does. The fields left out measure zero. */
    readonly analysis?: Partial<ChangeAnalysis>;
    /** The rules the Gate raised, in the order it raised them. */
    readonly factors?: readonly StakeFactor[];
    /** Why the change cannot be taken back. Empty means it can. */
    readonly irreversibilityReasons?: readonly IrreversibilityReason[];
};

buildAssessmentfunction

A complete ChangeAssessment from the handful of facts a test cares about.

const buildAssessment: (idFactory: IdFactory, draft: AssessmentDraft) => ChangeAssessment

Episode harness

testing/episode-harness

A write path wired to a real Gate, a real store and a real journal, with only the model scripted.

EpisodeHarnesstype

type EpisodeHarness = {
    readonly path: WritePath;
    readonly journal: TelemetryJournal;
    readonly collector: TelemetryCollector;
    readonly tree: LoomTree;
    readonly ids: IdFactory;
    readonly intent: EditIntent;
    readonly fold: () => Promise<EpisodeFold>;
};

removalDeltafunction

const removalDelta: (tree: LoomTree, ids: IdFactory, nodeId: string) => TreeDelta

harnessWithfunction

const harnessWith: (options: {
    readonly script: (tree: LoomTree, ids: IdFactory, intent: EditIntent) => Result<ProposedChange, InterpretationError>;
    readonly repairer?: (tree: LoomTree, ids: IdFactory, intent: EditIntent) => ChangeRepairer;
    readonly baseRevision?: number;
    /**
     * The Gate that judges this run. Defaults to the shipped one, which is what
     * most tests want; a test about attribution needs two hosts that differ, and
     * a policy is the only thing a disposition names.
     */
    readonly policy?: GatePolicy;
}) => Promise<EpisodeHarness>

proposalScriptfunction

Confidence is the only knob most of these tests need, because it is what the default policy dispositions on: high accepts, middling holds, low refuses.

const proposalScript: (confidence: number) => (tree: LoomTree, ids: IdFactory, intent: EditIntent) => Result<ProposedChange, InterpretationError>

Filesystem

testing/filesystem

A filesystem in memory, so CLI tests assert on what would be written rather than on what a temporary directory ended up containing. Failures are injectable because "wrote two files, then the disk refused" is a path the CLI has to report honestly and no real filesystem will produce on demand.

MemoryFileSystemtype

type MemoryFileSystem = FileSystem & {
    readonly files: ReadonlyMap<string, string>;
};

MemoryFileSystemOptionstype

type MemoryFileSystemOptions = {
    readonly existing?: readonly string[];
    readonly listFails?: string;
    readonly writeFailsAt?: string;
};

memoryFileSystemfunction

const memoryFileSystem: (options?: MemoryFileSystemOptions) => MemoryFileSystem

Fixtures

testing/fixtures

A small, deterministic tree used across tests and, later, by the renderer and portal for smoke fixtures. Ids come from a sequential factory so assertions can name them instead of digging them out of the structure.

SampleTreetype

type SampleTree = {
    readonly tree: LoomTree;
    readonly ids: {
        readonly page: NodeId;
        readonly header: NodeId;
        readonly headline: NodeId;
        readonly main: NodeId;
        readonly card: NodeId;
        readonly body: NodeId;
        readonly footer: NodeId;
    };
};

sampleTreefunction

const sampleTree: () => SampleTree

FormTreetype

type FormTree = {
    readonly tree: LoomTree;
    readonly ids: {
        readonly page: NodeId;
        readonly form: NodeId;
        /** A sibling that posts nowhere, so a test can move a destination onto one. */
        readonly aside: NodeId;
    };
};

formTreefunction

A page whose form already posts somewhere. Separate from sampleTree rather than a node added to it: every count in the stakes and analysis suites is asserted against that tree's exact size, and a fixture that grows is a fixture that makes unrelated tests wrong.

const formTree: (to?: string) => FormTree

Model replies

testing/model-replies

Reply bodies of the shape a model produces under the interpreter's output schema, kept as raw strings so tests exercise the parse step rather than starting from an already-decoded object. Node ids refer to sampleTree.

INSERT_NOTE_REPLYvalue

const INSERT_NOTE_REPLY: string

CONFIGURE_CARD_REPLYvalue

const CONFIGURE_CARD_REPLY: string

NO_CHANGE_REPLYvalue

const NO_CHANGE_REPLY: string

NOT_UNDERSTOOD_REPLYvalue

const NOT_UNDERSTOOD_REPLY: string

OFF_SCHEMA_REPLYvalue

Well-formed JSON, but not a shape the reply schema allows.

const OFF_SCHEMA_REPLY: string

FOREIGN_ID_REPLYvalue

An operation that names a node id the id scheme does not permit.

const FOREIGN_ID_REPLY: string

UNDECODABLE_PROP_REPLYvalue

Passes the schema, because the schema only says "a string", and fails the parse. This is the failure mode accepted in exchange for the grammar budget, so it is a fixture rather than an impossibility.

const UNDECODABLE_PROP_REPLY: string

NON_OBJECT_PROPS_REPLYvalue

Parseable JSON, but not an object, so it cannot be a prop bag.

const NON_OBJECT_PROPS_REPLY: string

INSERT_SLOT_REPLYvalue

Passes the schema and the parse, and proposes a slot — which the reply schema no longer offers as an insertable kind, so the draft schema must refuse it.

const INSERT_SLOT_REPLY: string

TRUNCATED_REPLYvalue

const TRUNCATED_REPLY = "{\"outcome\":\"change\",\"rationale\":\"Adds a not"

Primitives

testing/primitives

A host primitive set covering sampleTree, so renderer tests assert against real markup rather than a mock's call log.

undecoratedPrimitivevalue

A primitive that ignores loom.editable — the failure mode §4 has to catch.

const undecoratedPrimitive: LoomPrimitive

testPrimitivesvalue

const testPrimitives: Readonly<Record<string, LoomPrimitive>>

testPrimitiveResolvervalue

const testPrimitiveResolver: PrimitiveResolver