StatifierUI.Fixtures.Expectations (StatifierUI v0.4.0)

Copy Markdown View Source

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).

Summary

Types

One expectation's outcome.

Functions

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

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.

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

Types

result()

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

Functions

check(fixtures, opts \\ [])

@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!(fixtures, opts \\ [])

@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(fixtures, opts \\ [])

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