diff --git a/lib/test_dispatch.ex b/lib/test_dispatch.ex index c61c1a1..804032d 100644 --- a/lib/test_dispatch.ex +++ b/lib/test_dispatch.ex @@ -36,12 +36,7 @@ defmodule TestDispatch do {form, selector_type} = find_form(conn, entity_or_test_selector) selector_tuple = {selector_type, entity_or_test_selector} - form - |> find_inputs(selector_tuple) - |> Enum.map(&input_to_tuple(&1, selector_tuple)) - |> update_input_values(attrs) - |> prepend_entity(selector_tuple) - |> send_to_action(form, conn) + _dispatch_form(conn, form, selector_tuple, attrs) end def dispatch_form(conn, entity_or_test_selector, nil), @@ -57,6 +52,14 @@ defmodule TestDispatch do {form, _} = find_form(conn, test_selector) selector_tuple = {:entity, entity} + _dispatch_form(conn, form, selector_tuple, attrs) + end + + @spec submit_form(Plug.Conn.t(), %{}, binary() | atom() | nil) :: Plug.Conn.t() + def submit_form(conn, attrs \\ %{}, entity_or_test_selector \\ nil), + do: dispatch_form(conn, attrs, entity_or_test_selector) + + defp _dispatch_form(conn, form, selector_tuple, attrs) do form |> find_inputs(selector_tuple) |> Enum.map(&input_to_tuple(&1, selector_tuple)) @@ -65,10 +68,6 @@ defmodule TestDispatch do |> send_to_action(form, conn) end - @spec submit_form(Plug.Conn.t(), %{}, binary() | atom() | nil) :: Plug.Conn.t() - def submit_form(conn, attrs, entity_or_test_selector), - do: dispatch_form(conn, attrs, entity_or_test_selector) - @doc """ Works like `submit_form/3`. The test_selector is used to find the right form and the entity is used to find and fill the inputs correctly. @@ -78,6 +77,14 @@ defmodule TestDispatch do def submit_form(conn, attrs, entity, test_selector), do: dispatch_form(conn, attrs, entity, test_selector) + @spec submit_form(Plug.Conn.t(), %{}, binary()) :: Plug.Conn.t() + def submit_with_button(%Plug.Conn{} = conn, attrs \\ %{}, button_text) do + {form, _} = find_form(conn, button_text: button_text) + selector_tuple = {:button_text, button_text} + + _dispatch_form(conn, form, selector_tuple, attrs) + end + @doc """ Finds a link by a given conn, test_selector and an optional test_value. @@ -148,8 +155,6 @@ defmodule TestDispatch do |> find_link(test_selector, test_value) |> _dispatch_link(conn) - @spec dispatch_link(nil | Floki.html_tree(), Plug.Conn.t(), binary(), binary() | nil) :: - Plug.Conn.t() def click_link(conn, test_selector, test_value, tree), do: dispatch_link(conn, test_selector, test_value, tree) diff --git a/lib/test_dispatch/form.ex b/lib/test_dispatch/form.ex index 5f6a5e5..8cd1620 100644 --- a/lib/test_dispatch/form.ex +++ b/lib/test_dispatch/form.ex @@ -9,15 +9,43 @@ defmodule TestDispatch.Form do def find_inputs(form, {:entity, _} = entity_tuple), do: find_input_fields(form, entity_tuple) + def find_inputs(form, {:button_text, button_text}) do + button = find_buttons(form, button_text) + + if button, + do: [button | find_default_inputs(form)], + else: find_default_inputs(form) + end + def find_inputs(form, _) do + find_default_inputs(form) + end + + defp find_default_inputs(form) do fields = find_input_fields(form, "") selects = find_selects(form, "") textareas = find_textareas(form, "") radio_buttons = find_radio_buttons(form, "") - Enum.uniq(fields ++ selects ++ textareas ++ radio_buttons) end + def find_buttons(form, button_text), + do: + find_submit_input(form, button_text) || + find_submit_button(form, button_text) + + defp find_submit_button(form, button_text) do + form + |> Floki.find("button[type=submit]") + |> Enum.find(&contains_button_text?(&1, button_text)) + end + + defp find_submit_input(form, button_text) do + form + |> Floki.find("input[type=submit]") + |> Enum.find(&contains_button_text?(&1, button_text)) + end + def find_radio_buttons(form, "") do radios = Floki.find(form, "input[type=radio]") @@ -28,7 +56,7 @@ defmodule TestDispatch.Form do other_radios = radios |> Enum.uniq_by(fn {_, list, _} -> - list |> Enum.find(fn {key, _} -> "name" == key end) |> elem(1) + Enum.find(list, fn {key, _} -> "name" == key end) |> elem(1) end) |> Enum.reject(fn radio -> floki_attribute(radio, "name") in checked_names end) @@ -72,6 +100,9 @@ defmodule TestDispatch.Form do new_nested_list = update_nested_input_values(acc_nested_list, tuple, nested_attr) Map.put(acc, key, new_nested_list) + + {} -> + acc end end) end @@ -80,11 +111,13 @@ defmodule TestDispatch.Form do value = input |> elem(0) |> _input_to_tuple([input]) case input |> key_for_input(entity_tuple) |> resolve_nested() do + nil -> {} {key, index, nested_key} -> {key, index, {nested_key, value}} key -> {key, value} end end + defp _input_to_tuple("button", input), do: Floki.text(input) defp _input_to_tuple("textarea", input), do: Floki.text(input) defp _input_to_tuple("input", input), do: floki_attribute(input, "value") @@ -102,14 +135,19 @@ defmodule TestDispatch.Form do String.replace_prefix(key, "#{entity}_", "") end + defp key_for_input({"button", _, _} = input, _), do: floki_attribute(input, "name") + defp key_for_input(input, _) do + name = floki_attribute(input, "name") id = floki_attribute(input, "id") if floki_attribute(input, "type") == "radio", - do: String.replace_suffix(id, "_#{floki_attribute(input, "value")}", ""), + do: name, else: id end + defp resolve_nested(nil), do: nil + defp resolve_nested(key) do case Regex.split(~r{_\d*_}, key, include_captures: true) do [key, capture, nested_key] -> @@ -171,13 +209,27 @@ defmodule TestDispatch.Form do "The provided conn had the status #{status} that doesn't fall into the 2xx range" ) - def find_form_by(form, nil), do: {List.last(form), nil} + def find_form_by(forms, nil), do: {List.last(forms), nil} - def find_form_by(form, entity_or_test_selector) do - test_selector_result = Enum.find(form, &find_test_selector(&1, entity_or_test_selector)) + def find_form_by(forms, button_text: button_text) do + form = Enum.find(forms, &contains_button_text?(&1, button_text)) + + if form, + do: {form, :button_text}, + else: + raise(""" + No form found for the given button text: #{button_text} + Found the button texts: + + #{all_buttons(forms)} + """) + end + + def find_form_by(forms, entity_or_test_selector) do + test_selector_result = Enum.find(forms, &find_test_selector(&1, entity_or_test_selector)) entity_result = - Enum.find(form, &(&1 |> Floki.find("*[id^=#{entity_or_test_selector}_]") |> Enum.any?())) + Enum.find(forms, &(&1 |> Floki.find("*[id^=#{entity_or_test_selector}_]") |> Enum.any?())) cond do is_tuple(test_selector_result) -> @@ -204,4 +256,29 @@ defmodule TestDispatch.Form do |> html_response(status) |> Floki.parse_document!() end + + defp contains_button_text?(form, button_text) do + input_submit = + form + |> Floki.find("input[type=submit]") + |> Enum.any?(&(text(&1) == button_text)) + + button_submit = + form + |> Floki.find("button[type=submit]") + |> Enum.any?(&(text(&1) == button_text)) + + input_submit || button_submit + end + + def text(html_tree), + do: html_tree |> Floki.text() |> String.replace(~r/\s+/, " ") |> String.trim() + + defp all_buttons(html_tree) do + all_submit_buttons = html_tree |> Floki.find("button[type=submit]") + all_submit_inputs = html_tree |> Floki.find("input[type=submit]") + + (all_submit_inputs ++ all_submit_buttons) + |> Enum.map(&(text([&1]) <> "\n ")) + end end