# `Statifier.Testing.Case`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.1.1/lib/statifier/testing/case.ex#L1)

Test case template for the SCION and W3C conformance corpora.

Test-side surface for chart authors, versioned with the engine: no module in
`lib/` outside `Statifier.Testing.*` may reference anything inside it, so the
engine never consults the test harness to decide behavior (ADR-0053,
amending ADR-0006). This module is also the supported entry point for a
downstream application testing its own charts, not only the corpus's driver
- `use Statifier.Testing.Case` works the same way outside this repository as
it does inside it.

Every generated corpus test goes through `test_scxml/4` and nothing else, so
this module is the entire coupling surface between the corpus and the library
(ADR-0006). It needs exactly four things from `Statifier` to *drive* a chart:

1. parse an SCXML document,
2. build and initialize a state chart from it,
3. send an event synchronously and get the next state chart back,
4. read the active leaf-state set.

Each of those lives in its own private helper below - `parse_document/1`,
`initialize/1`, `send_event/2`, `active_leaf_states/1` - and each is a thin
adapter over `Statifier`'s four-function API (`Statifier.compile/1`,
`Statifier.initialize/2`, `Statifier.send_event/2`,
`Statifier.active_leaf_states/1`): unwrapping the `{:ok, _}` / `{:error, _}`
tuples and translating the corpus's event map into the shape `Statifier`
expects. The four-function contract stays a hard constraint on the library
surface rather than something the corpus can widen.

The assertion path reads two more things, both because a terminated chart's
`configuration` is empty by construction (`exit_interpreter/1`, Appendix D)
and the harness still has to say what the chart's leaves were at exit:

- `initialize/1` and `send_event/2` now also return the call's effect list.
  `assert_configuration/3` looks for a `{:done, _}` effect in it and, when
  found, restores that effect's `configuration` field - `Effect.Done`
  carries the terminal configuration - onto the `%MachineState{}` before
  reading leaves off it -
  so the terminal position is observed instead of the post-exit emptiness.
  This is still driving-side plumbing routed to the assertion, not a fifth
  driving function.
- `Statifier.MachineState.active_leaf_states/1` (the untranslated,
  index-keyed version behind `Statifier.active_leaf_states/1`) is read
  directly by `assert_every_leaf_named/2` to compare its size against the
  id-translated set, catching a nameless active leaf that the id-based
  comparison would silently drop. This is an inspection of a value the
  harness already holds, not a new way to drive the chart.

Documents using features v2 does not support flunk with the feature named
(`Statifier.Testing.FeatureDetector`) - they never skip, so an unimplemented
feature can never masquerade as a passing test. Both suites are excluded by
default (`:scion`, `:scxml_w3`), so `mix test` stays green while
`test.regression`'s ratchet is the thing that grows.

## Two driving paths, one closed contract

`test_scxml/4` routes each document, on the same detected feature set, to
one of two private paths. Documents that need real delivery, wall-clock
timers, or child sessions (`@session_features` below) drive through a
`Statifier.Session`; every other document drives synchronously through the
four functions this moduledoc opens with. The session path keeps
`Statifier.compile/1` and `Statifier.active_leaf_states/1` exactly as they
were and replaces the other two with five: `initialize` becomes
`Statifier.start_session/2`, `send_event` becomes
`Statifier.Session.send_event/2`, and driving a live session additionally
needs `Statifier.Session.snapshot/1` to read a configuration,
`Statifier.Session.status/1` to know when it has settled, and
`Statifier.Session.stop/2` to tear it down. Nine functions across the two
paths, enumerated in ADR-0006 and closed: adding a tenth, or a third
driving path, reopens that record rather than being a harness change.
Either way the corpus still cannot widen the library surface, because every
one of the nine is public API carried by its own record.

v1's `StateMachine` and logging helpers are deliberately not ported: the
subsystems they drove do not exist in v2, and reintroducing them here would
grow the coupling surface this module exists to hold flat.

# `session_required?`

```elixir
@spec session_required?(xml_or_detected :: String.t() | MapSet.t(atom())) :: boolean()
```

Whether a document needs the session layer to reach its expected
configurations. Public so the routing decision can be asserted directly;
`test_scxml/4` is the only caller in the corpus.

# `test_scxml`

```elixir
@spec test_scxml(
  xml :: String.t(),
  description :: String.t(),
  expected_initial_config :: [String.t()],
  events :: [{map(), [String.t()]}],
  opts :: keyword()
) :: :ok
```

Tests SCXML state chart behavior.

- `xml` - SCXML document string
- `description` - test description, for debugging
- `expected_initial_config` - active leaf state IDs expected after initialize
- `events` - list of `{event_map, expected_states}` tuples, where `event_map`
  carries the event name under `"name"`
- `opts` - see `Options` below

Flunks with the feature named if the document depends on an SCXML feature v2
does not support yet, rather than reporting a false pass.

## Options

Both default to values tuned for this repo's conformance corpus. A downstream
chart whose load-bearing delay is longer than the corpus's needs the knob.
Both are ignored on the synchronous path, which has no timing to tune.

- `:settle_window_ms` (default `100`) - how long to
  wait for pending timers to drain before sending the next event. Long enough
  to drain load-bearing intermediate delays, short enough never to let a guard
  send fire.
- `:configuration_deadline_ms` (default
  `4000`) - upper bound on waiting for a
  session to reach an expected configuration. Bounds only the wrong answer: a
  chart that cannot change again exits the poll immediately.

---

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