Packages

Native desktop GUI framework for Gleam, powered by Iced

Current section

Files

Jump to
plushie_gleam src plushie@testing@script.erl
Raw

src/plushie@testing@script.erl

-module(plushie@testing@script).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/plushie/testing/script.gleam").
-export([parse/1, parse_file/1]).
-export_type([header/0, backend/0, instruction/0, script/0]).
-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(
" Parser for `.plushie` test scripts.\n"
"\n"
" The `.plushie` format is a superset of iced's `.ice` test script format,\n"
" adding plushie-specific instructions like `expect` and `assert_model`.\n"
"\n"
" ## Format\n"
"\n"
" app: my_app\n"
" viewport: 800x600\n"
" theme: dark\n"
" backend: mock\n"
" -----\n"
" click \"#increment\"\n"
" expect \"Count: 1\"\n"
" tree_hash \"counter-at-1\"\n"
).
-type header() :: {header,
binary(),
{integer(), integer()},
binary(),
backend()}.
-type backend() :: mock_backend | headless_backend | windowed_backend.
-type instruction() :: {click, binary()} |
{type_text, binary(), binary()} |
{type_key, binary()} |
{press, binary()} |
{release, binary()} |
{move, binary()} |
{move_to, integer(), integer()} |
{toggle, binary()} |
{select, binary(), binary()} |
{slide, binary(), float()} |
{expect, binary()} |
{assert_tree_hash, binary()} |
{assert_screenshot, binary()} |
{assert_text, binary(), binary()} |
{assert_model, binary()} |
{wait, integer()}.
-type script() :: {script, header(), list(instruction())}.
-file("src/plushie/testing/script.gleam", 116).
-spec find_field(list({binary(), binary()}), binary()) -> gleam@option:option(binary()).
find_field(Fields, Key) ->
case Fields of
[] ->
none;
[{K, V} | Rest] ->
case K =:= Key of
true ->
{some, V};
false ->
find_field(Rest, Key)
end
end.
-file("src/plushie/testing/script.gleam", 127).
-spec parse_viewport(binary()) -> {integer(), integer()}.
parse_viewport(Str) ->
case gleam@string:split(Str, <<"x"/utf8>>) of
[W, H] ->
case {gleam_stdlib:parse_int(W), gleam_stdlib:parse_int(H)} of
{{ok, Wi}, {ok, Hi}} ->
{Wi, Hi};
{_, _} ->
{800, 600}
end;
_ ->
{800, 600}
end.
-file("src/plushie/testing/script.gleam", 138).
-spec parse_backend(binary()) -> backend().
parse_backend(Str) ->
case Str of
<<"mock"/utf8>> ->
mock_backend;
<<"headless"/utf8>> ->
headless_backend;
<<"windowed"/utf8>> ->
windowed_backend;
_ ->
mock_backend
end.
-file("src/plushie/testing/script.gleam", 83).
-spec parse_header(binary()) -> {ok, header()} | {error, binary()}.
parse_header(Text) ->
Fields = begin
_pipe = Text,
_pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
_pipe@2 = gleam@list:map(_pipe@1, fun gleam@string:trim/1),
_pipe@3 = gleam@list:filter(
_pipe@2,
fun(Line) ->
(Line /= <<""/utf8>>) andalso not gleam_stdlib:string_starts_with(
Line,
<<"#"/utf8>>
)
end
),
gleam@list:fold(
_pipe@3,
[],
fun(Acc, Line@1) ->
case gleam@string:split_once(Line@1, <<":"/utf8>>) of
{ok, {Key, Value}} ->
[{gleam@string:trim(Key), gleam@string:trim(Value)} |
Acc];
{error, _} ->
Acc
end
end
)
end,
case find_field(Fields, <<"app"/utf8>>) of
{some, App_str} ->
Viewport = case find_field(Fields, <<"viewport"/utf8>>) of
{some, V} ->
parse_viewport(V);
none ->
{800, 600}
end,
Theme = case find_field(Fields, <<"theme"/utf8>>) of
{some, T} ->
T;
none ->
<<"dark"/utf8>>
end,
Backend = case find_field(Fields, <<"backend"/utf8>>) of
{some, B} ->
parse_backend(B);
none ->
mock_backend
end,
{ok, {header, App_str, Viewport, Theme, Backend}};
none ->
{error, <<"header must include 'app:' field"/utf8>>}
end.
-file("src/plushie/testing/script.gleam", 212).
-spec parse_assertion(list(binary())) -> gleam@option:option(instruction()).
parse_assertion(Tokens) ->
case Tokens of
[<<"expect"/utf8>>, Text] ->
{some, {expect, Text}};
[<<"tree_hash"/utf8>>, Name] ->
{some, {assert_tree_hash, Name}};
[<<"screenshot"/utf8>>, Name@1] ->
{some, {assert_screenshot, Name@1}};
[<<"assert_text"/utf8>>, Selector, Text@1] ->
{some, {assert_text, Selector, Text@1}};
[<<"assert_model"/utf8>>, Expr] ->
{some, {assert_model, Expr}};
_ ->
none
end.
-file("src/plushie/testing/script.gleam", 223).
-spec parse_move_target(binary()) -> instruction().
parse_move_target(Target) ->
case gleam@string:split(Target, <<","/utf8>>) of
[X_str, Y_str] ->
case {gleam_stdlib:parse_int(gleam@string:trim(X_str)),
gleam_stdlib:parse_int(gleam@string:trim(Y_str))} of
{{ok, X}, {ok, Y}} ->
{move_to, X, Y};
{_, _} ->
{move, Target}
end;
_ ->
{move, Target}
end.
-file("src/plushie/testing/script.gleam", 234).
-spec parse_number(binary()) -> float().
parse_number(Str) ->
case gleam_stdlib:parse_float(Str) of
{ok, F} ->
F;
{error, _} ->
case gleam_stdlib:parse_int(Str) of
{ok, I} ->
erlang:float(I);
{error, _} ->
+0.0
end
end.
-file("src/plushie/testing/script.gleam", 191).
-spec parse_action(list(binary())) -> gleam@option:option(instruction()).
parse_action(Tokens) ->
case Tokens of
[<<"click"/utf8>>, Selector] ->
{some, {click, Selector}};
[<<"toggle"/utf8>>, Selector@1] ->
{some, {toggle, Selector@1}};
[<<"select"/utf8>>, Selector@2, Value] ->
{some, {select, Selector@2, Value}};
[<<"slide"/utf8>>, Selector@3, Value_str] ->
{some, {slide, Selector@3, parse_number(Value_str)}};
[<<"type"/utf8>>, Selector@4, Text] ->
{some, {type_text, Selector@4, Text}};
[<<"type"/utf8>>, Key] ->
{some, {type_key, Key}};
[<<"press"/utf8>>, Key@1] ->
{some, {press, Key@1}};
[<<"release"/utf8>>, Key@2] ->
{some, {release, Key@2}};
[<<"move"/utf8>>, Target] ->
{some, parse_move_target(Target)};
[<<"wait"/utf8>>, Ms_str] ->
case gleam_stdlib:parse_int(Ms_str) of
{ok, Ms} ->
{some, {wait, Ms}};
{error, _} ->
none
end;
_ ->
none
end.
-file("src/plushie/testing/script.gleam", 252).
-spec do_tokenize(list(binary()), boolean(), binary(), list(binary())) -> list(binary()).
do_tokenize(Chars, In_quote, Current, Tokens) ->
case Chars of
[] ->
case Current of
<<""/utf8>> ->
Tokens;
_ ->
[Current | Tokens]
end;
[<<"\\"/utf8>>, <<"\""/utf8>> | Rest] when In_quote ->
do_tokenize(Rest, true, <<Current/binary, "\""/utf8>>, Tokens);
[<<"\\"/utf8>>, <<"\\"/utf8>> | Rest@1] when In_quote ->
do_tokenize(Rest@1, true, <<Current/binary, "\\"/utf8>>, Tokens);
[<<"\""/utf8>> | Rest@2] ->
case In_quote of
true ->
do_tokenize(Rest@2, false, <<""/utf8>>, [Current | Tokens]);
false ->
do_tokenize(Rest@2, true, Current, Tokens)
end;
[<<" "/utf8>> | Rest@3] ->
case In_quote of
true ->
do_tokenize(
Rest@3,
true,
<<Current/binary, " "/utf8>>,
Tokens
);
false ->
case Current of
<<""/utf8>> ->
do_tokenize(Rest@3, false, <<""/utf8>>, Tokens);
_ ->
do_tokenize(
Rest@3,
false,
<<""/utf8>>,
[Current | Tokens]
)
end
end;
[<<"\t"/utf8>> | Rest@3] ->
case In_quote of
true ->
do_tokenize(
Rest@3,
true,
<<Current/binary, " "/utf8>>,
Tokens
);
false ->
case Current of
<<""/utf8>> ->
do_tokenize(Rest@3, false, <<""/utf8>>, Tokens);
_ ->
do_tokenize(
Rest@3,
false,
<<""/utf8>>,
[Current | Tokens]
)
end
end;
[C | Rest@4] ->
do_tokenize(Rest@4, In_quote, <<Current/binary, C/binary>>, Tokens)
end.
-file("src/plushie/testing/script.gleam", 247).
?DOC(
" Tokenize a line, respecting quoted strings.\n"
" \"click \\\"#foo\\\"\" -> [\"click\", \"#foo\"]\n"
).
-spec tokenize(binary()) -> list(binary()).
tokenize(Line) ->
_pipe = do_tokenize(gleam@string:to_graphemes(Line), false, <<""/utf8>>, []),
lists:reverse(_pipe).
-file("src/plushie/testing/script.gleam", 179).
-spec parse_instruction(binary()) -> {ok, instruction()} | {error, binary()}.
parse_instruction(Line) ->
Tokens = tokenize(Line),
case parse_action(Tokens) of
{some, Instr} ->
{ok, Instr};
none ->
case parse_assertion(Tokens) of
{some, Instr@1} ->
{ok, Instr@1};
none ->
{error, <<"unknown instruction: "/utf8, Line/binary>>}
end
end.
-file("src/plushie/testing/script.gleam", 149).
-spec parse_instructions(binary()) -> {ok, list(instruction())} |
{error, binary()}.
parse_instructions(Text) ->
Lines = begin
_pipe = Text,
_pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
_pipe@2 = gleam@list:map(_pipe@1, fun gleam@string:trim/1),
gleam@list:filter(
_pipe@2,
fun(Line) ->
(Line /= <<""/utf8>>) andalso not gleam_stdlib:string_starts_with(
Line,
<<"#"/utf8>>
)
end
)
end,
Results = gleam@list:map(Lines, fun parse_instruction/1),
Errors = gleam@list:filter_map(Results, fun(R) -> case R of
{error, Msg} ->
{ok, Msg};
{ok, _} ->
{error, nil}
end end),
case Errors of
[] ->
{ok, gleam@list:filter_map(Results, fun(R@1) -> case R@1 of
{ok, Instr} ->
{ok, Instr};
{error, _} ->
{error, nil}
end end)};
_ ->
{error, gleam@string:join(Errors, <<"\n"/utf8>>)}
end.
-file("src/plushie/testing/script.gleam", 70).
?DOC(" Parse a .plushie script from a string.\n").
-spec parse(binary()) -> {ok, script()} | {error, binary()}.
parse(Content) ->
case gleam@string:split_once(Content, <<"-----"/utf8>>) of
{ok, {Header_section, Body_section}} ->
gleam@result:'try'(
parse_header(Header_section),
fun(Header) ->
gleam@result:'try'(
parse_instructions(Body_section),
fun(Instructions) ->
{ok, {script, Header, Instructions}}
end
)
end
);
{error, _} ->
{error,
<<"missing ----- separator between header and instructions"/utf8>>}
end.
-file("src/plushie/testing/script.gleam", 62).
?DOC(" Parse a .plushie script from a file path.\n").
-spec parse_file(binary()) -> {ok, script()} | {error, binary()}.
parse_file(Path) ->
case plushie_snapshot_ffi:read_file(Path) of
{ok, Content} ->
parse(Content);
{error, _} ->
{error, <<"failed to read "/utf8, Path/binary>>}
end.