Current section
Files
Jump to
Current section
Files
src/plushie@testing.erl
-module(plushie@testing).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/plushie/testing.gleam").
-export([stop/1, model/1, tree/1, send_event/2, click/2, type_text/3, toggle/2, submit/2, slide/3, press_key/2, release_key/2, type_key/2, canvas_press/4, paste/3, sort/3, canvas_touch_press/5, canvas_touch_release/5, canvas_touch_move/5, select/3, find_by/2, find/2, element_text/1, element_prop/2, element_children/1, assert_exists/2, assert_not_exists/2, assert_text/3, resolved_a11y/2, assert_a11y/3, advance_frame/2, diagnostics/1, start/1]).
-export_type([test_context/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Test facade for plushie applications.\n"
"\n"
" Provides a unified test API that works on both BEAM and JS targets.\n"
" The backend is resolved once at `start` and carried through the\n"
" `TestContext`, so there are no repeated env lookups or backend construction.\n"
"\n"
" ## BEAM target\n"
"\n"
" Tests run against the real plushie-renderer binary. Set\n"
" `PLUSHIE_TEST_BACKEND` to choose the backend:\n"
"\n"
" gleam test # default: mock\n"
" PLUSHIE_TEST_BACKEND=headless gleam test # software rendering\n"
"\n"
" Default: `mock` (real binary in `--mock` mode, sessions pooled).\n"
"\n"
" ## JavaScript target\n"
"\n"
" Tests run the app's init/update/view cycle in-memory via the\n"
" pure session runner. No renderer binary is needed. Widget\n"
" interactions construct events directly.\n"
"\n"
" ## Usage\n"
"\n"
" let ctx = testing.start(my_app())\n"
" let ctx = testing.click(ctx, \"increment\")\n"
" let assert option.Some(el) = testing.find(ctx, \"count\")\n"
" should.equal(element.text(el), option.Some(\"Count: 1\"))\n"
" testing.stop(ctx)\n"
).
-opaque test_context(AAOF) :: {test_context,
plushie@testing@session:test_session(AAOF),
plushie@testing@backend:test_backend(AAOF)}.
-file("src/plushie/testing.gleam", 76).
?DOC(" Stop the test context and release resources.\n").
-spec stop(test_context(any())) -> nil.
stop(Ctx) ->
(erlang:element(3, erlang:element(3, Ctx)))(erlang:element(2, Ctx)).
-file("src/plushie/testing.gleam", 81).
?DOC(" Return the current model.\n").
-spec model(test_context(AAOM)) -> AAOM.
model(Ctx) ->
(erlang:element(20, erlang:element(3, Ctx)))(erlang:element(2, Ctx)).
-file("src/plushie/testing.gleam", 86).
?DOC(" Return the current normalized tree.\n").
-spec tree(test_context(any())) -> plushie@node:node_().
tree(Ctx) ->
(erlang:element(21, erlang:element(3, Ctx)))(erlang:element(2, Ctx)).
-file("src/plushie/testing.gleam", 91).
?DOC(" Dispatch a raw event through the update cycle.\n").
-spec send_event(test_context(AAOQ), plushie@event:event()) -> test_context(AAOQ).
send_event(Ctx, Event) ->
{test_context,
(erlang:element(23, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Event
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 98).
?DOC(" Simulate a click on a widget by ID.\n").
-spec click(test_context(AAOT), binary()) -> test_context(AAOT).
click(Ctx, Id) ->
{test_context,
(erlang:element(5, erlang:element(3, Ctx)))(erlang:element(2, Ctx), Id),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 103).
?DOC(" Simulate text input on a widget by ID.\n").
-spec type_text(test_context(AAOW), binary(), binary()) -> test_context(AAOW).
type_text(Ctx, Id, Text) ->
{test_context,
(erlang:element(6, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
Text
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 112).
?DOC(" Simulate a checkbox/toggler toggle by ID.\n").
-spec toggle(test_context(AAOZ), binary()) -> test_context(AAOZ).
toggle(Ctx, Id) ->
{test_context,
(erlang:element(8, erlang:element(3, Ctx)))(erlang:element(2, Ctx), Id),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 117).
?DOC(" Simulate form submission on a widget by ID.\n").
-spec submit(test_context(AAPC), binary()) -> test_context(AAPC).
submit(Ctx, Id) ->
{test_context,
(erlang:element(7, erlang:element(3, Ctx)))(erlang:element(2, Ctx), Id),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 122).
?DOC(" Simulate a slider change by ID.\n").
-spec slide(test_context(AAPF), binary(), float()) -> test_context(AAPF).
slide(Ctx, Id, Value) ->
{test_context,
(erlang:element(10, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
Value
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 133).
?DOC(
" Simulate a key press. Key string uses PascalCase wire format\n"
" (e.g., \"ArrowRight\", \"Escape\", \"Tab\") with optional modifier\n"
" prefixes (\"ctrl+s\", \"shift+Tab\").\n"
).
-spec press_key(test_context(AAPI), binary()) -> test_context(AAPI).
press_key(Ctx, Key) ->
{test_context,
(erlang:element(11, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Key
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 138).
?DOC(" Simulate a key release.\n").
-spec release_key(test_context(AAPL), binary()) -> test_context(AAPL).
release_key(Ctx, Key) ->
{test_context,
(erlang:element(12, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Key
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 143).
?DOC(" Simulate a key press and release.\n").
-spec type_key(test_context(AAPO), binary()) -> test_context(AAPO).
type_key(Ctx, Key) ->
{test_context,
(erlang:element(13, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Key
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 148).
?DOC(" Simulate a mouse press on a canvas widget at (x, y) coordinates.\n").
-spec canvas_press(test_context(AAPR), binary(), float(), float()) -> test_context(AAPR).
canvas_press(Ctx, Id, X, Y) ->
{test_context,
(erlang:element(14, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
X,
Y
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 158).
?DOC(" Simulate pasting text into a widget by ID.\n").
-spec paste(test_context(AAPU), binary(), binary()) -> test_context(AAPU).
paste(Ctx, Id, Text) ->
{test_context,
(erlang:element(15, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
Text
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 167).
?DOC(" Trigger sort on a table widget by ID and column name.\n").
-spec sort(test_context(AAPX), binary(), binary()) -> test_context(AAPX).
sort(Ctx, Id, Column) ->
{test_context,
(erlang:element(16, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
Column
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 176).
?DOC(" Simulate a touch press on a canvas widget at (x, y) with a finger index.\n").
-spec canvas_touch_press(
test_context(AAQA),
binary(),
float(),
float(),
integer()
) -> test_context(AAQA).
canvas_touch_press(Ctx, Id, X, Y, Finger) ->
{test_context,
(erlang:element(17, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
X,
Y,
Finger
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 190).
?DOC(" Simulate a touch release on a canvas widget at (x, y) with a finger index.\n").
-spec canvas_touch_release(
test_context(AAQD),
binary(),
float(),
float(),
integer()
) -> test_context(AAQD).
canvas_touch_release(Ctx, Id, X, Y, Finger) ->
{test_context,
(erlang:element(18, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
X,
Y,
Finger
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 204).
?DOC(" Simulate a touch move on a canvas widget at (x, y) with a finger index.\n").
-spec canvas_touch_move(
test_context(AAQG),
binary(),
float(),
float(),
integer()
) -> test_context(AAQG).
canvas_touch_move(Ctx, Id, X, Y, Finger) ->
{test_context,
(erlang:element(19, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
X,
Y,
Finger
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 218).
?DOC(" Simulate selection on a widget by ID.\n").
-spec select(test_context(AAQJ), binary(), binary()) -> test_context(AAQJ).
select(Ctx, Id, Value) ->
{test_context,
(erlang:element(9, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id,
Value
),
erlang:element(3, Ctx)}.
-file("src/plushie/testing.gleam", 289).
?DOC(" Find a window node by ID in the tree.\n").
-spec find_window_subtree(plushie@node:node_(), binary()) -> gleam@option:option(plushie@node:node_()).
find_window_subtree(Tree, Window_id) ->
case (erlang:element(3, Tree) =:= <<"window"/utf8>>) andalso (erlang:element(
2,
Tree
)
=:= Window_id) of
true ->
{some, Tree};
false ->
_pipe = gleam@list:find_map(
erlang:element(5, Tree),
fun(Child) -> case find_window_subtree(Child, Window_id) of
{some, N} ->
{ok, N};
none ->
{error, nil}
end end
),
gleam@option:from_result(_pipe)
end.
-file("src/plushie/testing.gleam", 304).
?DOC(" Find the first element whose content/label/value/placeholder matches.\n").
-spec find_by_prop_value(plushie@node:node_(), binary()) -> gleam@option:option(plushie@testing@element:element()).
find_by_prop_value(Tree, Text) ->
Keys = [<<"content"/utf8>>,
<<"label"/utf8>>,
<<"value"/utf8>>,
<<"placeholder"/utf8>>],
Matches = fun(Node) ->
gleam@list:any(
Keys,
fun(Key) ->
case gleam_stdlib:map_get(erlang:element(4, Node), Key) of
{ok, {string_val, V}} ->
V =:= Text;
_ ->
false
end
end
)
end,
case Matches(Tree) of
true ->
{some, plushie@testing@element:from_node(Tree)};
false ->
_pipe = gleam@list:find_map(
erlang:element(5, Tree),
fun(Child) -> case find_by_prop_value(Child, Text) of
{some, El} ->
{ok, El};
none ->
{error, nil}
end end
),
gleam@option:from_result(_pipe)
end.
-file("src/plushie/testing.gleam", 328).
?DOC(" Find the first element with a matching a11y field value.\n").
-spec find_by_a11y_field(plushie@node:node_(), binary(), binary()) -> gleam@option:option(plushie@testing@element:element()).
find_by_a11y_field(Tree, Field, Value) ->
Matches = fun(Node) ->
case gleam_stdlib:map_get(erlang:element(4, Node), <<"a11y"/utf8>>) of
{ok, {dict_val, A11y}} ->
case gleam_stdlib:map_get(A11y, Field) of
{ok, {string_val, V}} ->
V =:= Value;
_ ->
false
end;
_ ->
false
end
end,
case Matches(Tree) of
true ->
{some, plushie@testing@element:from_node(Tree)};
false ->
_pipe = gleam@list:find_map(
erlang:element(5, Tree),
fun(Child) -> case find_by_a11y_field(Child, Field, Value) of
{some, El} ->
{ok, El};
none ->
{error, nil}
end end
),
gleam@option:from_result(_pipe)
end.
-file("src/plushie/testing.gleam", 266).
?DOC(
" Search a tree for an element matching a Selector.\n"
"\n"
" Note: the Focused selector searches for an element with\n"
" a11y.focused = \"true\" in the tree props. For runtime-level\n"
" focus tracking, use plushie.get_focused() instead.\n"
).
-spec find_in_tree(plushie@node:node_(), plushie@testing@backend:selector()) -> gleam@option:option(plushie@testing@element:element()).
find_in_tree(Tree, Selector) ->
case Selector of
{by_id, Id} ->
plushie@testing@element:find(Tree, Id);
{by_text, Text} ->
find_by_prop_value(Tree, Text);
{by_role, Role} ->
find_by_a11y_field(Tree, <<"role"/utf8>>, Role);
{by_label, Label} ->
find_by_a11y_field(Tree, <<"label"/utf8>>, Label);
{in_window, Window_id, Inner_selector} ->
case find_window_subtree(Tree, Window_id) of
{some, Window_node} ->
find_in_tree(Window_node, Inner_selector);
none ->
none
end;
focused ->
case find_by_a11y_field(Tree, <<"focused"/utf8>>, <<"true"/utf8>>) of
{some, _} = Found ->
Found;
none ->
none
end
end.
-file("src/plushie/testing.gleam", 256).
?DOC(
" Find an element by a typed Selector.\n"
"\n"
" Searches the current tree for text content, a11y role/label,\n"
" or focus state. For ID-based lookup, use `find(ctx, \"id\")`.\n"
"\n"
" ```gleam\n"
" testing.find_by(ctx, backend.ByRole(\"button\"))\n"
" testing.find_by(ctx, backend.ByText(\"Save\"))\n"
" testing.find_by(ctx, backend.Focused)\n"
" ```\n"
).
-spec find_by(test_context(any()), plushie@testing@backend:selector()) -> gleam@option:option(plushie@testing@element:element()).
find_by(Ctx, Selector) ->
Tree = (erlang:element(21, erlang:element(3, Ctx)))(erlang:element(2, Ctx)),
find_in_tree(Tree, Selector).
-file("src/plushie/testing.gleam", 236).
?DOC(
" Find an element by ID or selector string.\n"
"\n"
" Accepts plain IDs (\"save\"), scoped IDs (\"form/save\"),\n"
" window-qualified IDs (\"main#save\"), pseudo-selectors (\":focused\"),\n"
" and attribute selectors (\"[role=button]\", \"[text=Save]\").\n"
"\n"
" Plain IDs delegate to the backend's find function (which may\n"
" query the renderer). Semantic selectors search the tree directly.\n"
).
-spec find(test_context(any()), binary()) -> gleam@option:option(plushie@testing@element:element()).
find(Ctx, Selector) ->
Parsed = plushie@testing@backend:parse_selector(Selector),
case Parsed of
{by_id, Id} ->
(erlang:element(4, erlang:element(3, Ctx)))(
erlang:element(2, Ctx),
Id
);
_ ->
find_by(Ctx, Parsed)
end.
-file("src/plushie/testing.gleam", 357).
?DOC(" Extract text content from an element.\n").
-spec element_text(plushie@testing@element:element()) -> gleam@option:option(binary()).
element_text(El) ->
plushie@testing@element:text(El).
-file("src/plushie/testing.gleam", 362).
?DOC(" Get a prop value from an element.\n").
-spec element_prop(plushie@testing@element:element(), binary()) -> gleam@option:option(plushie@node:prop_value()).
element_prop(El, Key) ->
plushie@testing@element:prop(El, Key).
-file("src/plushie/testing.gleam", 367).
?DOC(" Get an element's children.\n").
-spec element_children(plushie@testing@element:element()) -> list(plushie@testing@element:element()).
element_children(El) ->
plushie@testing@element:children(El).
-file("src/plushie/testing.gleam", 374).
?DOC(" Assert that an element with the given selector exists.\n").
-spec assert_exists(test_context(AAQZ), binary()) -> test_context(AAQZ).
assert_exists(Ctx, Selector) ->
case find(Ctx, Selector) of
{some, _} ->
Ctx;
none ->
erlang:error(#{gleam_error => panic,
message => (<<<<"Expected element '"/utf8, Selector/binary>>/binary,
"' to exist, but it was not found"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_exists"/utf8>>,
line => 381})
end.
-file("src/plushie/testing.gleam", 388).
?DOC(" Assert that no element with the given selector exists.\n").
-spec assert_not_exists(test_context(AARC), binary()) -> test_context(AARC).
assert_not_exists(Ctx, Selector) ->
case find(Ctx, Selector) of
none ->
Ctx;
{some, _} ->
erlang:error(#{gleam_error => panic,
message => (<<<<"Expected element '"/utf8, Selector/binary>>/binary,
"' to not exist, but it was found"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_not_exists"/utf8>>,
line => 395})
end.
-file("src/plushie/testing.gleam", 402).
?DOC(" Assert that an element's text matches the expected value.\n").
-spec assert_text(test_context(AARF), binary(), binary()) -> test_context(AARF).
assert_text(Ctx, Selector, Expected) ->
case find(Ctx, Selector) of
none ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"Expected element '"/utf8,
Selector/binary>>/binary,
"' to exist with text '"/utf8>>/binary,
Expected/binary>>/binary,
"', but element was not found"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_text"/utf8>>,
line => 409});
{some, El} ->
case plushie@testing@element:text(El) of
{some, Actual} when Actual =:= Expected ->
Ctx;
{some, Actual@1} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<<<<<"Expected text '"/utf8,
Expected/binary>>/binary,
"' on '"/utf8>>/binary,
Selector/binary>>/binary,
"', but got '"/utf8>>/binary,
Actual@1/binary>>/binary,
"'"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_text"/utf8>>,
line => 420});
none ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"Expected text '"/utf8,
Expected/binary>>/binary,
"' on '"/utf8>>/binary,
Selector/binary>>/binary,
"', but element has no text content"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_text"/utf8>>,
line => 430})
end
end.
-file("src/plushie/testing.gleam", 448).
?DOC(
" Return the resolved a11y dict for a widget.\n"
"\n"
" Layers render-pipeline inference (placeholder -> description for\n"
" text-entry widgets, alt -> label for media widgets) on top of the\n"
" normalized `a11y` prop so tests see what assistive technology will\n"
" see. Panics if the selector doesn't match any widget.\n"
).
-spec resolved_a11y(test_context(any()), binary()) -> gleam@dict:dict(binary(), plushie@node:prop_value()).
resolved_a11y(Ctx, Selector) ->
case find(Ctx, Selector) of
{some, El} ->
plushie@testing@element:resolved_a11y(El);
none ->
erlang:error(#{gleam_error => panic,
message => (<<<<"Expected element '"/utf8, Selector/binary>>/binary,
"' to exist"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"resolved_a11y"/utf8>>,
line => 454})
end.
-file("src/plushie/testing.gleam", 463).
?DOC(
" Assert that a widget's resolved a11y contains all expected keys.\n"
"\n"
" Reads through [`resolved_a11y`](#resolved_a11y) so inferred defaults\n"
" (placeholder -> description, alt -> label) compose with the\n"
" author's explicit overrides.\n"
).
-spec assert_a11y(
test_context(AARM),
binary(),
list({binary(), plushie@node:prop_value()})
) -> test_context(AARM).
assert_a11y(Ctx, Selector, Expected) ->
Actual = resolved_a11y(Ctx, Selector),
gleam@list:each(
Expected,
fun(Pair) ->
{Key, Want} = Pair,
case gleam_stdlib:map_get(Actual, Key) of
{ok, Got} when Got =:= Want ->
nil;
{ok, _} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"assert_a11y: a11y."/utf8,
Key/binary>>/binary,
" mismatch for '"/utf8>>/binary,
Selector/binary>>/binary,
"'"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_a11y"/utf8>>,
line => 474});
{error, _} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"assert_a11y: a11y."/utf8,
Key/binary>>/binary,
" not found on '"/utf8>>/binary,
Selector/binary>>/binary,
"'"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"assert_a11y"/utf8>>,
line => 478})
end
end
),
Ctx.
-file("src/plushie/testing.gleam", 491).
?DOC(
" Dispatch an AnimationFrame event to advance frame-based animations.\n"
"\n"
" This works with the mock and session backends. On headless/windowed\n"
" backends, the renderer generates its own animation frames and this\n"
" function has no effect.\n"
).
-spec advance_frame(test_context(AARQ), integer()) -> test_context(AARQ).
advance_frame(Ctx, Timestamp) ->
send_event(Ctx, {system, {animation_frame, Timestamp}}).
-file("src/plushie/testing.gleam", 501).
?DOC(
" Get diagnostic events from the test context.\n"
" This is a placeholder that returns an empty list; it will be\n"
" expanded when runtime telemetry diagnostic interception is added.\n"
).
-spec diagnostics(test_context(any())) -> list(binary()).
diagnostics(_) ->
[].
-file("src/plushie/testing.gleam", 516).
-spec get_or_start_pooled(plushie@testing@session_pool:pool_mode()) -> plushie@testing@backend:test_backend(any()).
get_or_start_pooled(Mode) ->
case plushie_test_pool_ffi:get_pool() of
{ok, Pool_subject} ->
plushie@testing@backend@mock:backend(Pool_subject);
{error, _} ->
Config = begin
_record = plushie@testing@session_pool:default_config(),
{pool_config,
Mode,
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record)}
end,
Pool_subject@2 = case plushie@testing@session_pool:start(Config) of
{ok, Pool_subject@1} -> Pool_subject@1;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"plushie/testing"/utf8>>,
function => <<"get_or_start_pooled"/utf8>>,
line => 522,
value => _assert_fail,
start => 15792,
'end' => 15848,
pattern_start => 15803,
pattern_end => 15819})
end,
plushie_test_pool_ffi:put_pool(Pool_subject@2),
plushie@testing@backend@mock:backend(Pool_subject@2)
end.
-file("src/plushie/testing.gleam", 508).
-spec resolve_backend() -> plushie@testing@backend:test_backend(any()).
resolve_backend() ->
case plushie_ffi:get_env(<<"PLUSHIE_TEST_BACKEND"/utf8>>) of
{ok, <<"headless"/utf8>>} ->
get_or_start_pooled(headless);
_ ->
get_or_start_pooled(mock)
end.
-file("src/plushie/testing.gleam", 69).
?DOC(
" Start a test session for a simple app (msg = Event).\n"
"\n"
" On BEAM, requires the plushie-renderer binary (panics with\n"
" setup instructions if not found). On JS, runs in-memory.\n"
).
-spec start(plushie@app:app(AAOG, plushie@event:event())) -> test_context(AAOG).
start(App) ->
Be = resolve_backend(),
Session = (erlang:element(2, Be))(App),
{test_context, Session, Be}.