Skip to content

Commit d6bcecb

Browse files
committed
Submit forms with a button
- This allows users to specify the button they want to submit the form with. - If the button that's submitted has a `name`it will show up in the params.
1 parent b5b10b5 commit d6bcecb

File tree

2 files changed

+98
-17
lines changed

2 files changed

+98
-17
lines changed

lib/test_dispatch.ex

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ defmodule TestDispatch do
3636
{form, selector_type} = find_form(conn, entity_or_test_selector)
3737
selector_tuple = {selector_type, entity_or_test_selector}
3838

39+
_dispatch_form(conn, form, selector_tuple, attrs)
40+
end
41+
42+
def dispatch_form(conn, entity_or_test_selector, nil),
43+
do: dispatch_form(conn, %{}, entity_or_test_selector)
44+
45+
defp _dispatch_form(conn, form, selector_tuple, attrs) do
3946
form
4047
|> find_inputs(selector_tuple)
4148
|> Enum.map(&input_to_tuple(&1, selector_tuple))
@@ -44,9 +51,6 @@ defmodule TestDispatch do
4451
|> send_to_action(form, conn)
4552
end
4653

47-
def dispatch_form(conn, entity_or_test_selector, nil),
48-
do: dispatch_form(conn, %{}, entity_or_test_selector)
49-
5054
@doc """
5155
Works like `dispatch/3`. The test_selector is used to find the right form and the
5256
entity is used to find and fill the inputs correctly.
@@ -57,16 +61,11 @@ defmodule TestDispatch do
5761
{form, _} = find_form(conn, test_selector)
5862
selector_tuple = {:entity, entity}
5963

60-
form
61-
|> find_inputs(selector_tuple)
62-
|> Enum.map(&input_to_tuple(&1, selector_tuple))
63-
|> update_input_values(attrs)
64-
|> prepend_entity(selector_tuple)
65-
|> send_to_action(form, conn)
64+
_dispatch_form(conn, form, selector_tuple, attrs)
6665
end
6766

6867
@spec submit_form(Plug.Conn.t(), %{}, binary() | atom() | nil) :: Plug.Conn.t()
69-
def submit_form(conn, attrs, entity_or_test_selector),
68+
def submit_form(conn, attrs \\ %{}, entity_or_test_selector \\ nil),
7069
do: dispatch_form(conn, attrs, entity_or_test_selector)
7170

7271
@doc """
@@ -78,6 +77,14 @@ defmodule TestDispatch do
7877
def submit_form(conn, attrs, entity, test_selector),
7978
do: dispatch_form(conn, attrs, entity, test_selector)
8079

80+
@spec submit_form(Plug.Conn.t(), %{}, binary()) :: Plug.Conn.t()
81+
def submit_with_button(%Plug.Conn{} = conn, attrs \\ %{}, button_text) do
82+
{form, _} = find_form(conn, button_text: button_text)
83+
selector_tuple = {:button_text, button_text}
84+
85+
_dispatch_form(conn, form, selector_tuple, attrs)
86+
end
87+
8188
@doc """
8289
Finds a link by a given conn, test_selector and an optional test_value.
8390
@@ -148,8 +155,6 @@ defmodule TestDispatch do
148155
|> find_link(test_selector, test_value)
149156
|> _dispatch_link(conn)
150157

151-
@spec dispatch_link(nil | Floki.html_tree(), Plug.Conn.t(), binary(), binary() | nil) ::
152-
Plug.Conn.t()
153158
def click_link(conn, test_selector, test_value, tree),
154159
do: dispatch_link(conn, test_selector, test_value, tree)
155160

lib/test_dispatch/form.ex

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,43 @@ defmodule TestDispatch.Form do
99
def find_inputs(form, {:entity, _} = entity_tuple),
1010
do: find_input_fields(form, entity_tuple)
1111

12+
def find_inputs(form, {:button_text, button_text}) do
13+
button = find_buttons(form, button_text)
14+
15+
if button,
16+
do: [button | find_default_inputs(form)],
17+
else: find_default_inputs(form)
18+
end
19+
1220
def find_inputs(form, _) do
21+
find_default_inputs(form)
22+
end
23+
24+
defp find_default_inputs(form) do
1325
fields = find_input_fields(form, "")
1426
selects = find_selects(form, "")
1527
textareas = find_textareas(form, "")
1628
radio_buttons = find_radio_buttons(form, "")
17-
1829
Enum.uniq(fields ++ selects ++ textareas ++ radio_buttons)
1930
end
2031

32+
def find_buttons(form, button_text),
33+
do:
34+
find_submit_input(form, button_text) ||
35+
find_submit_button(form, button_text)
36+
37+
defp find_submit_button(form, button_text) do
38+
form
39+
|> Floki.find("button[type=submit]")
40+
|> Enum.find(&contains_button_text?(&1, button_text))
41+
end
42+
43+
defp find_submit_input(form, button_text) do
44+
form
45+
|> Floki.find("input[type=submit]")
46+
|> Enum.find(&contains_button_text?(&1, button_text))
47+
end
48+
2149
def find_radio_buttons(form, "") do
2250
radios = Floki.find(form, "input[type=radio]")
2351

@@ -72,6 +100,9 @@ defmodule TestDispatch.Form do
72100
new_nested_list = update_nested_input_values(acc_nested_list, tuple, nested_attr)
73101

74102
Map.put(acc, key, new_nested_list)
103+
104+
{} ->
105+
acc
75106
end
76107
end)
77108
end
@@ -80,11 +111,13 @@ defmodule TestDispatch.Form do
80111
value = input |> elem(0) |> _input_to_tuple([input])
81112

82113
case input |> key_for_input(entity_tuple) |> resolve_nested() do
114+
nil -> {}
83115
{key, index, nested_key} -> {key, index, {nested_key, value}}
84116
key -> {key, value}
85117
end
86118
end
87119

120+
defp _input_to_tuple("button", input), do: Floki.text(input)
88121
defp _input_to_tuple("textarea", input), do: Floki.text(input)
89122
defp _input_to_tuple("input", input), do: floki_attribute(input, "value")
90123

@@ -102,6 +135,8 @@ defmodule TestDispatch.Form do
102135
String.replace_prefix(key, "#{entity}_", "")
103136
end
104137

138+
defp key_for_input({"button", _, _} = input, _), do: floki_attribute(input, "name")
139+
105140
defp key_for_input(input, _) do
106141
id = floki_attribute(input, "id")
107142

@@ -110,6 +145,8 @@ defmodule TestDispatch.Form do
110145
else: id
111146
end
112147

148+
defp resolve_nested(nil), do: nil
149+
113150
defp resolve_nested(key) do
114151
case Regex.split(~r{_\d*_}, key, include_captures: true) do
115152
[key, capture, nested_key] ->
@@ -171,13 +208,27 @@ defmodule TestDispatch.Form do
171208
"The provided conn had the status #{status} that doesn't fall into the 2xx range"
172209
)
173210

174-
def find_form_by(form, nil), do: {List.last(form), nil}
211+
def find_form_by(forms, nil), do: {List.last(forms), nil}
175212

176-
def find_form_by(form, entity_or_test_selector) do
177-
test_selector_result = Enum.find(form, &find_test_selector(&1, entity_or_test_selector))
213+
def find_form_by(forms, button_text: button_text) do
214+
form = Enum.find(forms, &contains_button_text?(&1, button_text))
215+
216+
if form,
217+
do: {form, :button_text},
218+
else:
219+
raise("""
220+
No form found for the given button text: #{button_text}
221+
Found the button texts:
222+
223+
#{all_buttons(forms)}
224+
""")
225+
end
226+
227+
def find_form_by(forms, entity_or_test_selector) do
228+
test_selector_result = Enum.find(forms, &find_test_selector(&1, entity_or_test_selector))
178229

179230
entity_result =
180-
Enum.find(form, &(&1 |> Floki.find("*[id^=#{entity_or_test_selector}_]") |> Enum.any?()))
231+
Enum.find(forms, &(&1 |> Floki.find("*[id^=#{entity_or_test_selector}_]") |> Enum.any?()))
181232

182233
cond do
183234
is_tuple(test_selector_result) ->
@@ -204,4 +255,29 @@ defmodule TestDispatch.Form do
204255
|> html_response(status)
205256
|> Floki.parse_document!()
206257
end
258+
259+
defp contains_button_text?(form, button_text) do
260+
input_submit =
261+
form
262+
|> Floki.find("input[type=submit]")
263+
|> Enum.any?(&(text(&1) == button_text))
264+
265+
button_submit =
266+
form
267+
|> Floki.find("button[type=submit]")
268+
|> Enum.any?(&(text(&1) == button_text))
269+
270+
input_submit || button_submit
271+
end
272+
273+
def text(html_tree),
274+
do: html_tree |> Floki.text() |> String.replace(~r/\s+/, " ") |> String.trim()
275+
276+
defp all_buttons(html_tree) do
277+
all_submit_buttons = html_tree |> Floki.find("button[type=submit]")
278+
all_submit_inputs = html_tree |> Floki.find("input[type=submit]")
279+
280+
(all_submit_inputs ++ all_submit_buttons)
281+
|> Enum.map(&(text([&1]) <> "\n "))
282+
end
207283
end

0 commit comments

Comments
 (0)