Skip to content

Commit

Permalink
Add LiveViewTest.put_submitter/2 (#2598)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcrumm authored May 16, 2023
1 parent 69a1a04 commit 7581dcd
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 4 deletions.
45 changes: 43 additions & 2 deletions lib/phoenix_live_view/test/client_proxy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1056,8 +1056,10 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
end)
|> Enum.reduce(%{}, &form_defaults/2)

case fill_in_map(Enum.to_list(element.form_data || %{}), "", node, []) do
{:ok, value} -> {:ok, DOM.deep_merge(defaults, value)}
with {:ok, defaults} <- maybe_submitter(defaults, type, node, element),
{:ok, value} <- fill_in_map(Enum.to_list(element.form_data || %{}), "", node, []) do
{:ok, DOM.deep_merge(defaults, value)}
else
{:error, _, _} = error -> error
end

Expand All @@ -1073,6 +1075,45 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
{:ok, DOM.all_values(node)}
end

defp maybe_submitter(defaults, :submit, form, %Element{meta: %{submitter: element}}) do
collect_submitter(form, element, defaults)
end

defp maybe_submitter(defaults, _, _, _), do: {:ok, defaults}

defp collect_submitter(form, element, defaults) do
case select_node(form, element) do
{:ok, node} -> collect_submitter(node, form, element, defaults)
{:error, _, msg} -> {:error, :invalid, "invalid form submitter, " <> msg}
end
end

defp collect_submitter(node, form, element, defaults) do
name = DOM.attribute(node, "name")

cond do
is_nil(name) ->
{:error, :invalid,
"form submitter selected by #{inspect(element.selector)} must have a name"}

submitter?(node) and is_nil(DOM.attribute(node, "disabled")) ->
{:ok, Plug.Conn.Query.decode_pair({name, DOM.attribute(node, "value")}, defaults)}

true ->
{:error, :invalid,
"could not find non-disabled submit input or button with name #{inspect(name)} within:\n\n" <>
DOM.inspect_html(DOM.all(form, "[name]"))}
end
end

defp submitter?({"input", _, _} = node) do
DOM.attribute(node, "type") == "submit"
end

defp submitter?({"button", _, _} = node) do
DOM.attribute(node, "type") in ["submit", nil]
end

defp maybe_push_events(diff, state) do
case diff do
%{@events => events} ->
Expand Down
42 changes: 40 additions & 2 deletions lib/phoenix_live_view/test/live_view_test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,37 @@ defmodule Phoenix.LiveViewTest do
render_event(view, :click, event, value)
end

@doc """
Puts the submitter `element_or_selector` on the given `form` element.
A submitter is an element that initiates the form's submit event on the client. When a submitter
is put on an element created with `form/3` and then the form is submitted via `render_submit/2`,
the name/value pair of the submitter will be included in the submit event payload.
The given element or selector must exist within the form and match one of the following:
- A `button` or `input` element with `type="submit"`.
- A `button` element without a `type` attribute.
## Examples
form = view |> form("#my-form")
assert form
|> put_submitter("button[name=example]")
|> render_submit() =~ "Submitted example"
"""
def put_submitter(form, element_or_selector)

def put_submitter(%Element{proxy: proxy} = form, submitter) when is_binary(submitter) do
put_submitter(form, %Element{proxy: proxy, selector: submitter})
end

def put_submitter(%Element{} = form, %Element{} = submitter) do
%{form | meta: Map.put(form.meta, :submitter, submitter)}
end

@doc """
Sends a form submit event given by `element` and returns the rendered result.
Expand All @@ -594,8 +625,15 @@ defmodule Phoenix.LiveViewTest do
To submit a form along with some with hidden input values:
assert view
|> form("#term", user: %{name: "hello"})
|> render_submit(%{user: %{"hidden_field" => "example"}}) =~ "Name updated"
|> form("#term", user: %{name: "hello"})
|> render_submit(%{user: %{"hidden_field" => "example"}}) =~ "Name updated"
To submit a form by a specific submit element via `put_submitter/2`:
assert view
|> form("#term", user: %{name: "hello"})
|> put_submitter("button[name=example_action]")
|> render_submit() =~ "Action taken"
"""
def render_submit(element, value \\ %{})
Expand Down
100 changes: 100 additions & 0 deletions test/phoenix_live_view/integrations/elements_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,15 @@ defmodule Phoenix.LiveView.ElementsTest do
end
end

test "put_submitter/2 puts submitter meta on element", %{live: view} do
selector = "button[name=submitter]"

from_element = view |> element("form") |> put_submitter(element(view, selector))
from_selector = view |> element("form") |> put_submitter(selector)

assert from_element.meta.submitter == from_selector.meta.submitter
end

describe "render_submit" do
test "raises if element is not a form", %{live: view} do
assert_raise ArgumentError, "phx-submit is only allowed in forms, got \"a\"", fn ->
Expand All @@ -426,6 +435,97 @@ defmodule Phoenix.LiveView.ElementsTest do
assert view |> element("#empty-form") |> render_submit(%{"foo" => "bar"})
assert last_event(view) =~ ~s|form-submit: %{"foo" => "bar"}|
end

test "raises on invalid submitter", %{live: view} do
assert_raise ArgumentError, ~r"invalid form submitter", fn ->
assert view
|> element("#submitter-form")
|> put_submitter("#element-does-not-exist")
|> render_submit()
end

assert_raise ArgumentError, ~r"invalid form submitter", fn ->
assert view
|> element("#submitter-form")
|> put_submitter("button")
|> render_submit()
end

assert_raise ArgumentError,
~r"form submitter selected by \"#input_no_name\" must have a name",
fn ->
assert view
|> element("#submitter-form")
|> put_submitter("#input_no_name")
|> render_submit()
end

assert_raise ArgumentError,
~r"could not find non-disabled submit input or button with name \"input_disabled\"",
fn ->
assert view
|> element("#submitter-form")
|> put_submitter("[name=input_disabled]")
|> render_submit()
end

assert_raise ArgumentError,
~r"could not find non-disabled submit input or button with name \"button_disabled\"",
fn ->
assert view
|> element("#submitter-form")
|> put_submitter("[name=button_disabled]")
|> render_submit()
end

assert_raise ArgumentError,
~r"could not find non-disabled submit input or button with name \"button_no_submit\"",
fn ->
assert view
|> element("#submitter-form")
|> put_submitter("[name=button_no_submit]")
|> render_submit()
end
end

test "includes the submitter key/value pair in the payload", %{live: view} do
assert view
|> element("#submitter-form")
|> put_submitter("[name=input]")
|> render_submit()

assert last_event(view) =~ ~s|form-submit: %{"data" => %{"a" => "b"}, "input" => "yes"}|

assert view
|> element("#submitter-form")
|> put_submitter("input#data-nested")
|> render_submit()

assert last_event(view) =~ ~s|form-submit: %{"data" => %{"a" => "b", "nested" => "yes"}}|

assert view
|> element("#submitter-form")
|> put_submitter("[name=button]")
|> render_submit()

assert last_event(view) =~ ~s|form-submit: %{"button" => "yes", "data" => %{"a" => "b"}}|

assert view
|> element("#submitter-form")
|> put_submitter("[name=button_no_type]")
|> render_submit()

assert last_event(view) =~
~s|form-submit: %{"button_no_type" => "yes", "data" => %{"a" => "b"}}|

assert view
|> element("#submitter-form")
|> put_submitter("[name=button_no_value]")
|> render_submit()

assert last_event(view) =~
~s|form-submit: %{"button_no_value" => "", "data" => %{"a" => "b"}}|
end
end

describe "follow_trigger_action" do
Expand Down
13 changes: 13 additions & 0 deletions test/support/live_views/elements.ex
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
<input name="hello[individual]" type="text" phx-change="individual-changed"/>
</form>
<form id="submitter-form" phx-submit="form-submit">
<input name="data[a]" type="hidden" value="b">
<input name="input" type="submit" value="yes">
<input name="input_disabled" type="submit" value="yes" disabled>
<input name="data[nested]" id="data-nested" type="submit" value="yes">
<input id="input_no_name" type="submit" value="yes">
<button name="button" type="submit" value="yes">button</button>
<button name="button_disabled" type="submit" value="yes" disabled />
<button name="button_no_submit" type="button" value="this_value_should_never_appear">button_no_submit</button>
<button name="button_no_type" value="yes">button_no_type</button>
<button name="button_no_value">Button No Value</button>
</form>
<form id="trigger-form-default" phx-submit="form-submit-trigger"
phx-trigger-action={@trigger_action}>
</form>
Expand Down

0 comments on commit 7581dcd

Please sign in to comment.