@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
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