# `StatifierUI.Fixtures.Expectations`
[🔗](https://github.com/riddler/statifier-ui/blob/v0.11.0/lib/statifier_ui/fixtures/expectations.ex#L1)

Runs every `expect` entry a fixture bundle states, evaluates it against its
named dataset, and reports whether the stated value held (ADR-0006). This
is the executable side of the fixture contract: `StatifierUI.Fixtures.Lint`
answers "is this bundle well-formed", this module answers "did every stated
expectation come true".

A test helper, not a mix task - see the implementation plan for the
reasoning. A host wires `check!/2` into one `ExUnit` test over its own
bundle, and the host's suite goes red the moment a dataset or an
expectation drifts from what its expressions actually evaluate to.

Comparison canonicalizes both the stated `expect` value and the evaluated
result through `StatifierUI.Value.encode/1` before comparing with `===`.
This is not cosmetic: `Predicator.evaluate/3` returns a duration as a
seven-key map (the unit predicator's parser never emits,
`:milliseconds`, is simply absent), while a `$duration` `expect` value
decodes through `StatifierUI.Value.decode/1` to all eight units filled in.
A bare `==` would report a spurious mismatch on every duration-valued
expectation; encoding both sides first canonicalizes them to the same
eight-key shape. Comparing on the encoded form also keeps `1` and `1.0`
distinct, which matters because JSON does.

`expect` values live in predicator's closed value domain, and so does
every value `Predicator.evaluate/3` produces, so a genuine encode failure
on either side means something reached this module from outside that
domain - reported as `{:unsupported_value, term}` rather than raised.

`run/2` never raises and never returns an error tuple: an evaluation
failure is data, exactly as `Predicator.evaluate/3` itself treats it
(CLAUDE.md's errors-are-values rule).

# `result`

```elixir
@type result() :: %{
  expression: String.t(),
  dataset: String.t(),
  source: String.t(),
  expected: term(),
  actual: term() | nil,
  status: :match | :mismatch | :error | :missing_dataset,
  error: struct() | {:unsupported_value, term()} | nil
}
```

One expectation's outcome.

`:match` and `:mismatch` are comparisons that happened; `:error` is an
evaluation that returned `{:error, e}` (or a value on either side outside
predicator's value domain); `:missing_dataset` is an `expect` key naming no
dataset in the bundle - a fact about the bundle, not an evaluation, so it
carries no `actual` or `error`.

# `check`

```elixir
@spec check(
  StatifierUI.Fixtures.t(),
  keyword()
) :: :ok | {:error, [result()]}
```

Partitions `run/2`'s results: anything that is not `:match` is a failure,
including `:missing_dataset`.

This intentionally differs from `StatifierUI.Fixtures.Lint`, which reports
a dangling `expect` key as a warning per ADR-0006. Lint asks whether the
bundle is well-formed enough to author against; this asks whether every
stated expectation was actually checked, and one naming no dataset never
was.

# `check!`

```elixir
@spec check!(
  StatifierUI.Fixtures.t(),
  keyword()
) :: :ok
```

Like `check/2`, but raises `StatifierUI.Fixtures.ExpectationError` on any
failure instead of returning one, with a message naming every failure's
expression, dataset, expected value, actual value, and error.

# `run`

```elixir
@spec run(
  StatifierUI.Fixtures.t(),
  keyword()
) :: [result()]
```

Evaluates every `(expression, dataset)` pair with a stated `expect`, in
sorted expression-then-dataset order so output is stable across runs.

`opts` forwards only `:functions` and `:providers` to
`Predicator.evaluate/3` - nothing else is a fixture-runner concern. Never
raises, never returns `{:error, _}`: a failure is a `:error` result.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
