Skip to main content
On this pageFunctions

Story

Functions

expectNoOutMessage

functionsource
/** Asserts that the OutMessage is None. */
(): (simulation: StorySimulation<Model, Message, Option.Option<OutMessage>>) => StorySimulation<Model, Message, Option.Option<OutMessage>>

expectOutMessage

functionsource
/** Asserts that the OutMessage is Some with the expected value. */
<OutMessage>(expected: OutMessage): (simulation: StorySimulation<Model, Message, Option.Option<OutMessage>>) => StorySimulation<Model, Message, Option.Option<OutMessage>>

message

functionsource
/** Sends a Message through update. Commands stay pending until resolve or resolveAll. */
<Message>(message_: NoInfer<Message>): (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>

model

functionsource
/** Runs an assertion function against the current Model. */
<Model, Message, OutMessage = undefined>(f: (model: Model) => void): (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>

with

functionsource
/** Sets the initial Model for a test story. */
<Model>(model: Model): WithStep<Model>

Types

CommandMatcher

typesource
/**
 * Pattern for matching a Command in test assertions. A Definition matches
 *  by name only ("a Command with this identity was dispatched"); an Instance
 *  matches by name AND structural-equal args ("a Command with this identity
 *  AND these args was dispatched"). Choose the form per assertion based on
 *  whether the test cares about the args value.
 * 
 *  Two modes only: name-only or name + full args. Partial-args matching is
 *  intentionally unsupported. If a subset of args carries the meaning the
 *  test is verifying, the right assertion is usually against the Model that
 *  the Command's result fed through update, not a partial Command shape.
 */
type CommandMatcher = CommandDefinition<string, unknown> | AnyCommand

StorySimulation

typesource
/** An immutable test simulation of a Foldkit program. */
type StorySimulation = Readonly<{
  commands: ReadonlyArray<AnyCommand>
  model: Model
  outMessage: OutMessage
}>

StoryStep

typesource
/** A single step in a story — either a WithStep or a simulation transform. */
type StoryStep = WithStep<NoInfer<Model>> | (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>

WithStep

typesource
/** A callable step that sets the initial Model. Carries phantom type for compile-time validation. */
type WithStep = Readonly<{
  _phantomModel: Model
}> & (simulation: StorySimulation<M, Message, OutMessage>) => StorySimulation<M, Message, OutMessage>

Constants

Command

constsource
/**
 * Steps that operate on the pending Commands of a story simulation.
 *  Destructure as `const { Command } = Story` for concise call sites.
 */
const Command: {
  expectExact: (matchers: readonly Array<CommandMatcher>) => (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>
  expectHas: (matchers: readonly Array<CommandMatcher>) => (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>
  expectNone: () => (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>
  resolve: (definition: CommandDefinition<Name, ResultMessage>, resultMessage: ResultMessage) => (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>
  resolveAll: (resolvers: {
    [K in string | number | symbol]: Resolver<R[K]>
  }) => (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>
}

story

constsource
/** Executes a test story. Throws if any Commands remain unresolved. */
const story: (updateFn: (model: Model, message: Message) => readonly [
  Model,
  readonly Array<Readonly<{
    args: Record<string, unknown>
    name: string
  }>>,
  OutMessage
], steps: readonly Array<StoryStep<Model, Message, OutMessage>>) => void

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson