Example data a host supplies for a chart: named scenario datamodels, example event payloads, and named datasets for evaluating expressions (ADR-0003, ADR-0006).
A fixture bundle is a set of maps:
scenarios- a scenario name (for example"within-budget-account") mapped to a complete example of the host-supplied datamodel for that situation.events- an event name (for example"authorize.approved") mapped to a sample_event.datapayload for that event.datasets- a dataset name mapped to a situation for evaluating a free-standing expression against.
A scenario and a dataset are not the same thing, even though both are string-keyed example data. A scenario is a complete example of the host-supplied datamodel for running a chart; it stands in for the whole state a chart would actually see. A dataset exists only to evaluate expressions and may be as small as the expression needs - a two-key map naming just the variables a single guard reads, with nothing else a real chart run would carry. ADR-0006 keeps datasets separate from scenarios rather than asking a scenario to double as both, so a dataset can stay minimal and a scenario can stay a faithful example.
Two delivery paths converge on this one struct: a host module implementing
StatifierUI.Fixtures.Source (from_source/1), and a JSON sidecar next to
a chart (a later phase's loader). Every consumer downstream of either path
is indifferent to which one produced the struct.
new/1 validates rather than raises: scenario datamodels must have string
keys at every level, so a fixture that would blow up deep inside the engine
is instead rejected at load time. Event payloads are preserved verbatim,
including the three-way :undefined / nil / map distinction predicator's
_event.data semantics require.
Scenario validation is deliberately stricter than the engine's, not a
mirror of it. Statifier.MachineState.new/2 rejects only atom keys that
are not booleans, so it accepts an integer or boolean key; this rejects
every key that is not a string. The reason is convergence: a JSON object
key is always a string, so a scenario a behaviour source could supply but
a sidecar could never express would break the one-struct guarantee
ADR-0003 rests on. Rejecting it in both paths keeps them interchangeable.
A dataset is validated exactly as a scenario is - string keys at every
depth, for the same convergence reason - so a predicator duration cannot
appear inside a dataset either: it decodes to a bare atom-keyed map, and
the same rule that rejects it inside a scenario
(test/support/fixtures/tagged_source.ex) rejects it here.
expressions is the fourth map: an expression name mapped to a
free-standing predicator "source" string and an optional "expect" map
of expected results, keyed by dataset name. Unlike a scenario or a dataset,
an expression entry is not walked for string keys - it is validated only
for the shape ADR-0006 fixes (a "source" string, an "expect" map with
binary keys), because its expect values are predicator values in their
own right and a duration is a legal one there, unlike inside a datamodel.
An absent "expect" entry for a dataset name means no expectation is
stated for that dataset - not that the expression is expected to be
undefined against it - and expect/3 returns :error for both that case
and an unknown expression name, since neither is a bundle error.
Summary
Types
An example datamodel: string keys at every level.
The name of a fixture dataset.
A diagnostic surfaced while loading a fixture bundle. path is the key
path inside the bundle; source is the file it came from, or nil for a
bundle that never was a file.
The name of a fixture event, matching an SCXML event name.
A free-standing expression fixture: a predicator "source" string and an
optional "expect" map keyed by dataset name (ADR-0006). Keys beyond those
two are preserved verbatim.
The name of a fixture expression.
The name of a fixture scenario.
Functions
Fetches a dataset by name.
Dataset names, sorted (ADR-0005's canonical-order rule).
Fetches an event's sample payload by name. Returns :error when the event
has no fixture, distinct from an event whose fixture value is nil or
:undefined.
Event names, sorted (ADR-0005's canonical-order rule).
Fetches the expected result stated for expression_name against
dataset_name.
Fetches an expression by name.
Expression names, sorted (ADR-0005's canonical-order rule).
Builds a validated fixture bundle from a StatifierUI.Fixtures.Source
module.
Builds a validated fixture bundle from :scenarios, :events,
:datasets, and :expressions options.
Fetches a scenario's datamodel by name.
Scenario names, sorted (ADR-0005's canonical-order rule).
Types
An example datamodel: string keys at every level.
@type dataset_name() :: String.t()
The name of a fixture dataset.
@type diagnostic() :: %{ kind: atom(), message: String.t(), path: [String.t()], source: Path.t() | nil }
A diagnostic surfaced while loading a fixture bundle. path is the key
path inside the bundle; source is the file it came from, or nil for a
bundle that never was a file.
@type event_name() :: String.t()
The name of a fixture event, matching an SCXML event name.
A free-standing expression fixture: a predicator "source" string and an
optional "expect" map keyed by dataset name (ADR-0006). Keys beyond those
two are preserved verbatim.
@type expression_name() :: String.t()
The name of a fixture expression.
@type scenario_name() :: String.t()
The name of a fixture scenario.
@type t() :: %StatifierUI.Fixtures{ datasets: %{optional(dataset_name()) => datamodel()}, diagnostics: [diagnostic()], events: %{optional(event_name()) => term()}, expressions: %{optional(expression_name()) => expression()}, scenarios: %{optional(scenario_name()) => datamodel()} }
Functions
@spec dataset(t(), dataset_name()) :: {:ok, datamodel()} | :error
Fetches a dataset by name.
@spec dataset_names(t()) :: [dataset_name()]
Dataset names, sorted (ADR-0005's canonical-order rule).
@spec event(t(), event_name()) :: {:ok, term()} | :error
Fetches an event's sample payload by name. Returns :error when the event
has no fixture, distinct from an event whose fixture value is nil or
:undefined.
@spec event_names(t()) :: [event_name()]
Event names, sorted (ADR-0005's canonical-order rule).
@spec expect(t(), expression_name(), dataset_name()) :: {:ok, term()} | :error
Fetches the expected result stated for expression_name against
dataset_name.
Returns :error both when expression_name names no expression and when
the expression exists but states no expectation for dataset_name - an
absent expect key means no expectation is stated (ADR-0006), which is not
an error condition, so the two cases are deliberately not distinguished.
@spec expression(t(), expression_name()) :: {:ok, expression()} | :error
Fetches an expression by name.
@spec expression_names(t()) :: [expression_name()]
Expression names, sorted (ADR-0005's canonical-order rule).
Builds a validated fixture bundle from a StatifierUI.Fixtures.Source
module.
Calls module.scenarios() and module.example_events(), and - when the
module implements the optional datasets/0 and expressions/0 callbacks -
module.datasets() and module.expressions(), and routes all of them
through new/1, so this path and the sidecar loader share exactly one
validation implementation. Returns {:error, {:not_a_source, module}} when
module does not implement the behaviour.
Builds a validated fixture bundle from :scenarios, :events,
:datasets, and :expressions options.
All four options default to %{}. Returns {:error, reason} rather than
raising when a scenario, event, or dataset key is not a string, when a
scenario or dataset datamodel is not a map, or when one contains a
non-string key at any depth. A non-string key found inside a datamodel is
reported as {:invalid_key, key, path}, where path starts with the
scenario or dataset name - or, when the offending map is a predicator
duration, as {:duration_in_scenario, path} or {:duration_in_dataset, path}, which says why rather than naming a unit key the author never
wrote.
Examples
iex> StatifierUI.Fixtures.new(scenarios: %{"within-budget-account" => %{"currency" => "USD"}})
{:ok, %StatifierUI.Fixtures{scenarios: %{"within-budget-account" => %{"currency" => "USD"}}}}
@spec scenario(t(), scenario_name()) :: {:ok, datamodel()} | :error
Fetches a scenario's datamodel by name.
@spec scenario_names(t()) :: [scenario_name()]
Scenario names, sorted (ADR-0005's canonical-order rule).