telega/testing/graph
Graph export for dialogs and flows — see the whole navigation map before running the bot.
import telega/testing/context
import telega/testing/graph
graph.of_dialog(dialog: booking_dialog(), ctx: context.context(session: Nil))
|> graph.to_dot
|> io.println
Dialog graphs are probed, not guessed. A window’s render is pure and
its handlers are pure functions of the state, so the exporter renders every
window, presses every button it finds (widget buttons included, routed the
way the engine routes them) and records where the returned DialogAction
points. Sub-dialogs are entered through the very init/result functions
the engine uses, so their windows are probed with real sub state.
Probing sees one state at a time: a Goto that only happens once the state
says so is invisible until you probe that state. The same goes for text
windows that validate their input — a rejected sample only ever draws the
re-render. Pass sample states and texts your handlers accept to
of_dialog_probing to widen the sweep.
Probing runs your handlers. Windows are pure by contract, but a handler
that writes to a database or calls the API on its way to a Done will do
exactly that while the graph is built — so hand it a test context (mock
client, test database), never a production one.
Flow graphs are declarative. A flow’s transitions are returned by its
handlers (Next, GoTo, …), which are effectful and cannot be probed, so
only what the builder knows is drawn: steps, conditionals, parallel
fan-out/join, subflows, and the transitions the author declared with
flow/builder.declare_next / declare_choice / declare_complete /
declare_cancel. Steps left without any declared edge are marked
OpaqueNode (dashed) — the honest signal that their navigation is only
visible in the handler.
Both graphs are deterministic strings, so they snapshot well:
graph.of_dialog(dialog:, ctx:) |> graph.to_mermaid |> birdie.snap(title: "booking:graph")
Types
pub type EdgeKind {
Declared
Probed
Unknown
}
Constructors
-
DeclaredDeclared in the builder: guaranteed to exist regardless of state.
-
ProbedFound by probing pure handlers with sample states: real, but only as complete as the sampled states.
-
UnknownThe edge exists, its target is computed at runtime (history-based
Back).
pub type Node {
Node(
id: String,
label: String,
kind: NodeKind,
group: option.Option(String),
)
}
Constructors
-
Node( id: String, label: String, kind: NodeKind, group: option.Option(String), )
pub type NodeKind {
EntryNode
StepNode
InputNode
OpaqueNode
TerminalNode
ExternalNode
}
Constructors
-
EntryNodeSynthetic start marker.
-
StepNodeA dialog window or a flow step.
-
InputNodeA dialog window that also accepts text input.
-
OpaqueNodeA flow step whose transitions are decided inside the handler and cannot be extracted statically.
-
TerminalNodeSynthetic end marker (
done,back, subflowreturn). -
ExternalNodeA URL or web-app target outside the bot.
Values
pub fn default_texts() -> List(String)
Default sample text fed to on_text windows while probing.
pub fn of_dialog(
dialog dialog: dialog.Dialog(
state,
session,
error,
dependencies,
),
ctx ctx: bot.Context(session, error, dependencies),
) -> Graph
Build the navigation graph of a dialog, probing every window with its initial state.
pub fn of_dialog_probing(
dialog dialog: dialog.Dialog(
state,
session,
error,
dependencies,
),
ctx ctx: bot.Context(session, error, dependencies),
states states: List(state),
texts texts: List(String),
) -> Graph
Build the navigation graph of a dialog, probing every window with the
initial state plus states, and every text window with texts.
Extra states uncover state-dependent navigation: a window that renders a “continue” button only once a choice is made contributes its edge only when probed with a state that has one.
pub fn of_flow(
flow flow: types.Flow(step_type, session, error, dependencies),
) -> Graph
Build the declarative skeleton of a flow: steps, conditional branches, parallel fan-out/join and subflows.
Transitions returned by step handlers (Next, GoTo, Complete) are not
visible here — handlers are effectful and are never called. Steps with no
declared outgoing edge are marked OpaqueNode.
pub fn to_dot(graph graph: Graph) -> String
Render the graph as Graphviz DOT. Pipe it to dot -Tsvg.
pub fn to_mermaid(graph graph: Graph) -> String
Render the graph as a Mermaid flowchart. Renders inline in GitHub and in
the docs without a local Graphviz.