Current section

Files

Jump to
lustre src lustre@dev@simulate.erl
Raw

src/lustre@dev@simulate.erl

-module(lustre@dev@simulate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/lustre/dev/simulate.gleam").
-export([simple/3, application/3, start/2, message/2, problem/3, model/1, view/1, history/1, event/4, click/2, input/3, submit/3]).
-export_type([app/3, simulation/2, event/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.
-opaque app(TCD, TCE, TCF) :: {app,
fun((TCD) -> {TCE, lustre@effect:effect(TCF)}),
fun((TCE, TCF) -> {TCE, lustre@effect:effect(TCF)}),
fun((TCE) -> lustre@vdom@vnode:element(TCF))}.
-opaque simulation(TCG, TCH) :: {simulation,
fun((TCG, TCH) -> {TCG, lustre@effect:effect(TCH)}),
fun((TCG) -> lustre@vdom@vnode:element(TCH)),
list(event(TCH)),
TCG,
lustre@vdom@vnode:element(TCH)}.
-type event(TCI) :: {dispatch, TCI} |
{event, lustre@dev@query:'query'(), binary(), gleam@json:json()} |
{problem, binary(), binary()}.
-file("src/lustre/dev/simulate.gleam", 81).
?DOC(
" Construct a simulated simple Lustre application. The simulation can be started\n"
" with the [`start`](#start) function by providing the initial arguments for\n"
" your app's `init` function.\n"
"\n"
" DOM events and messages dispatched by effects can be simulated using the\n"
" [`event`](#event) and [`messgae`](#message) functions.\n"
).
-spec simple(
fun((TCJ) -> TCK),
fun((TCK, TCL) -> TCK),
fun((TCK) -> lustre@vdom@vnode:element(TCL))
) -> app(TCJ, TCK, TCL).
simple(Init, Update, View) ->
{app,
fun(Args) -> {Init(Args), lustre@effect:none()} end,
fun(Model, Msg) -> {Update(Model, Msg), lustre@effect:none()} end,
View}.
-file("src/lustre/dev/simulate.gleam", 104).
?DOC(
" Construct a simulated Lustre application. The simulation can be started\n"
" with the [`start`](#start) function by providing the initial arguments for\n"
" your app's `init` function.\n"
"\n"
" DOM events and messages dispatched by effects can be simulated using the\n"
" [`event`](#event) and [`messgae`](#message) functions.\n"
"\n"
" > **Note**: simulated apps do not run any effects! You can simulate the result\n"
" > of an effect by using the [`message`](#message) function, but to test side\n"
" > effects you should test your application in a real environment.\n"
).
-spec application(
fun((TCQ) -> {TCR, lustre@effect:effect(TCS)}),
fun((TCR, TCS) -> {TCR, lustre@effect:effect(TCS)}),
fun((TCR) -> lustre@vdom@vnode:element(TCS))
) -> app(TCQ, TCR, TCS).
application(Init, Update, View) ->
{app, Init, Update, View}.
-file("src/lustre/dev/simulate.gleam", 118).
?DOC(
" Start a simulated Lustre application. Once a simulation is running you can\n"
" use the [`message`](#message) and [`event`](#event) functions to simulate\n"
" events\n"
).
-spec start(app(TCZ, TDA, TDB), TCZ) -> simulation(TDA, TDB).
start(App, Args) ->
{Model, _} = (erlang:element(2, App))(Args),
Html = (erlang:element(4, App))(Model),
{simulation,
erlang:element(3, App),
erlang:element(4, App),
[],
Model,
Html}.
-file("src/lustre/dev/simulate.gleam", 157).
?DOC(
" Simulate a message sent directly to the runtime. This is often used to mimic\n"
" the result of some effect you would have run in a real environment. For example,\n"
" you might simulate a click event on a login button and then simulate the\n"
" successful response from the server by calling this function with the message\n"
" you would dispatch from the effect:\n"
"\n"
" ```gleam\n"
" import birdie\n"
" import lustre/dev/simulate\n"
" import lustre/dev/query\n"
" import lustre/element\n"
"\n"
" pub fn login_test() {\n"
" let app = simulate.application(init:, update:, view:)\n"
" let login_button = query.element(matching: query.id(\"login\"))\n"
" let user = User(name: \"Lucy\")\n"
"\n"
" simulate.start(app, Nil)\n"
" |> simulate.event(on: login_button, name: \"click\", data: [])\n"
" // Simulate a successful response from the server\n"
" |> simulate.message(ApiReturnedUser(Ok(user)))\n"
" |> simulate.view\n"
" |> element.to_readable_string\n"
" |> birdie.snap(\"Successful login\")\n"
" }\n"
" ```\n"
"\n"
" > **Note**: your app's `view` function will probably be rendering quite a lot\n"
" > of HTML! To make your snapshots more meaningful, you might want to couple\n"
" > this with the [`query`](./query.html) module to only snapshot parts of the\n"
" > page that are relevant to the test.\n"
).
-spec message(simulation(TDH, TDI), TDI) -> simulation(TDH, TDI).
message(Simulation, Msg) ->
{Model, _} = (erlang:element(2, Simulation))(
erlang:element(5, Simulation),
Msg
),
Html = (erlang:element(3, Simulation))(Model),
History = [{dispatch, Msg} | erlang:element(4, Simulation)],
_record = Simulation,
{simulation,
erlang:element(2, _record),
erlang:element(3, _record),
History,
Model,
Html}.
-file("src/lustre/dev/simulate.gleam", 318).
?DOC(
" Log a problem that occured during the simulation. This function is useful for\n"
" external packages that want to provide functions to simulate certain effects\n"
" that may fail in the real world. For example, a routing package may log a\n"
" problem if a link has an invalid `href` attribute that would cause no message\n"
" to be dispatched.\n"
"\n"
" > **Note**: logging a problem will not stop the simulation from running, just\n"
" > like a real application!\n"
).
-spec problem(simulation(TEN, TEO), binary(), binary()) -> simulation(TEN, TEO).
problem(Simulation, Name, Message) ->
History = [{problem, Name, Message} | erlang:element(4, Simulation)],
_record = Simulation,
{simulation,
erlang:element(2, _record),
erlang:element(3, _record),
History,
erlang:element(5, _record),
erlang:element(6, _record)}.
-file("src/lustre/dev/simulate.gleam", 333).
?DOC(
" Introspect the current `model` of a running simulation. This can be useful\n"
" to debug why a simulation is not producing the view you expect.\n"
).
-spec model(simulation(TET, any())) -> TET.
model(Simulation) ->
erlang:element(5, Simulation).
-file("src/lustre/dev/simulate.gleam", 342).
?DOC(
" Introspect the current `view` of a running simulation. Typically you would\n"
" use this with a snapshot testing library like [`birdie`](https://hexdocs.pm/birdie/index.html)\n"
" and/or with the [`query`](./query.html) api to make assertions about the state\n"
" of the page.\n"
).
-spec view(simulation(any(), TEY)) -> lustre@vdom@vnode:element(TEY).
view(Simulation) ->
erlang:element(6, Simulation).
-file("src/lustre/dev/simulate.gleam", 354).
?DOC(
" Receive the current [`Event`](#Event) log of a running simulation. You can\n"
" use this to produce more detailed snapshots by also rendering the sequence of\n"
" events that produced the given view.\n"
"\n"
" In addition to simulated DOM events and message dispatch, the event log will\n"
" also include entries for when the queried event target could not be found in\n"
" the view and cases where an event was fired but not handled by your application.\n"
).
-spec history(simulation(any(), TFD)) -> list(event(TFD)).
history(Simulation) ->
_pipe = erlang:element(4, Simulation),
lists:reverse(_pipe).
-file("src/lustre/dev/simulate.gleam", 181).
?DOC(
" Simulate a DOM event on the first element that matches the given query. The\n"
" payload represents a simulated event object, and should be used to pass data\n"
" you expect your event handlers to decode.\n"
"\n"
" If no element matches the query, an [`EventTargetNotFound`](#Event) event is\n"
" logged in the simulation history. If an element is found, but the application\n"
" has no handler for the event, the [`EventHandlerNotFound`](#Event) event is\n"
" logged instead.\n"
"\n"
" > **Note**: this is not a perfect simulation of a real DOM event. There is no\n"
" > capture phase of a simulated event and simulated events will not bubble up\n"
" > to parent elements.\n"
).
-spec event(
simulation(TDN, TDO),
lustre@dev@query:'query'(),
binary(),
list({binary(), gleam@json:json()})
) -> simulation(TDN, TDO).
event(Simulation, Query, Event, Payload) ->
gleam@result:unwrap_both(
begin
gleam@result:'try'(
gleam@result:replace_error(
lustre@dev@query:find_path(
erlang:element(6, Simulation),
Query,
0,
root
),
problem(
Simulation,
<<"EventTargetNotFound"/utf8>>,
<<"No element matching "/utf8,
(lustre@dev@query:to_readable_string(Query))/binary>>
)
),
fun(_use0) ->
{_, Path} = _use0,
Events = lustre@vdom@events:from_node(
erlang:element(6, Simulation)
),
Data = gleam@json:object(Payload),
gleam@result:'try'(
gleam@result:replace_error(
gleam@pair:second(
lustre@vdom@events:handle(
Events,
lustre@vdom@path:to_string(Path),
Event,
begin
_pipe = Data,
_pipe@1 = gleam@json:to_string(_pipe),
_pipe@2 = gleam@json:parse(
_pipe@1,
{decoder,
fun gleam@dynamic@decode:decode_dynamic/1}
),
gleam@result:unwrap(
_pipe@2,
gleam@function:identity(nil)
)
end
)
),
problem(
Simulation,
<<"EventHandlerNotFound"/utf8>>,
<<<<<<"No "/utf8, Event/binary>>/binary,
" handler for element matching "/utf8>>/binary,
(lustre@dev@query:to_readable_string(Query))/binary>>
)
),
fun(Handler) ->
{Model, _} = (erlang:element(2, Simulation))(
erlang:element(5, Simulation),
erlang:element(4, Handler)
),
Html = (erlang:element(3, Simulation))(Model),
History = [{event, Query, Event, Data} |
erlang:element(4, Simulation)],
{ok,
begin
_record = Simulation,
{simulation,
erlang:element(2, _record),
erlang:element(3, _record),
History,
Model,
Html}
end}
end
)
end
)
end
).
-file("src/lustre/dev/simulate.gleam", 241).
?DOC(
" A convenience function that simulates a click event on the first element\n"
" matching the given query. This event will have no payload and is only\n"
" appropriate for event handlers that use Lustre's `on_click` handler or custom\n"
" handlers that do not decode the event payload.\n"
).
-spec click(simulation(TDU, TDV), lustre@dev@query:'query'()) -> simulation(TDU, TDV).
click(Simulation, Query) ->
event(Simulation, Query, <<"click"/utf8>>, []).
-file("src/lustre/dev/simulate.gleam", 262).
?DOC(
" Simulate an input event on the first element matching the given query. This\n"
" helper has an event payload that looks like this:\n"
"\n"
" ```json\n"
" {\n"
" \"target\": {\n"
" \"value\": value\n"
" }\n"
" }\n"
" ```\n"
"\n"
" and is appropriate for event handlers that use Lustre's `on_input` handler\n"
" or custom handlers that only decode the event target value.\n"
).
-spec input(simulation(TEA, TEB), lustre@dev@query:'query'(), binary()) -> simulation(TEA, TEB).
input(Simulation, Query, Value) ->
event(
Simulation,
Query,
<<"input"/utf8>>,
[{<<"target"/utf8>>,
gleam@json:object(
[{<<"value"/utf8>>, gleam@json:string(Value)}]
)}]
).
-file("src/lustre/dev/simulate.gleam", 289).
?DOC(
" Simulate a submit event on the first element matching the given query. The\n"
" simulated event payload looks like this:\n"
"\n"
" ```json\n"
" {\n"
" \"detail\": {\n"
" \"formData\": [\n"
" ...\n"
" ]\n"
" }\n"
" }\n"
" ```\n"
"\n"
" and is appropriate for event handlers that use Lustre's `on_submit` handler\n"
" or custom handlers that only decode the non-standard `detail.formData`\n"
" property.\n"
).
-spec submit(
simulation(TEG, TEH),
lustre@dev@query:'query'(),
list({binary(), binary()})
) -> simulation(TEG, TEH).
submit(Simulation, Query, Form_data) ->
event(
Simulation,
Query,
<<"submit"/utf8>>,
[{<<"detail"/utf8>>,
gleam@json:object(
[{<<"formData"/utf8>>,
gleam@json:array(
Form_data,
fun(Entry) ->
gleam@json:preprocessed_array(
[gleam@json:string(
erlang:element(1, Entry)
),
gleam@json:string(
erlang:element(2, Entry)
)]
)
end
)}]
)}]
).