diff --git a/lib/phoenix_test/html/data_attribute_form.ex b/lib/phoenix_test/html/data_attribute_form.ex new file mode 100644 index 00000000..11b06987 --- /dev/null +++ b/lib/phoenix_test/html/data_attribute_form.ex @@ -0,0 +1,55 @@ +defmodule PhoenixTest.Html.DataAttributeForm do + @moduledoc false + + alias PhoenixTest.Html + + def build({_, _, _} = element) do + method = Html.attribute(element, "data-method") + action = Html.attribute(element, "data-to") + csrf_token = Html.attribute(element, "data-csrf") + + %{} + |> Map.put(:method, method) + |> Map.put(:action, action) + |> Map.put(:csrf_token, csrf_token) + |> Map.put(:element, element) + |> Map.put(:data, %{"_csrf_token" => csrf_token, "_method" => method}) + end + + def validate!(form, selector, text) do + method = form.method + action = form.action + csrf_token = form.csrf_token + + missing = + ["data-method": method, "data-to": action, "data-csrf": csrf_token] + |> Enum.filter(fn {_, value} -> empty?(value) end) + + unless method && action && csrf_token do + raise ArgumentError, """ + Tried submitting form via `data-method` but some data attributes are + missing. + + I expected #{inspect(selector)} with text #{inspect(text)} to include + data-method, data-to, and data-csrf. + + I found: + + #{Html.raw(form.element)} + + It seems these are missing: #{Enum.map_join(missing, ", ", fn {key, _} -> key end)}. + + NOTE: `data-method` form submissions happen through JavaScript. Tests + emulate that, but be sure to verify you're including Phoenix.HTML.js! + + See: https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library + """ + end + + form + end + + defp empty?(value) do + value == "" || value == nil + end +end diff --git a/lib/phoenix_test/static.ex b/lib/phoenix_test/static.ex index fb475736..b21dd891 100644 --- a/lib/phoenix_test/static.ex +++ b/lib/phoenix_test/static.ex @@ -48,13 +48,26 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Static do end def click_link(session, selector, text) do - path = - session - |> render_html() - |> Query.find!(selector, text) - |> Html.attribute("href") + if data_attribute_form?(session, selector, text) do + form = + session + |> render_html() + |> Query.find!(selector, text) + |> Html.DataAttributeForm.build() + |> Html.DataAttributeForm.validate!(selector, text) + + session.conn + |> dispatch(@endpoint, form.method, form.action, form.data) + |> maybe_redirect(session) + else + path = + session + |> render_html() + |> Query.find!(selector, text) + |> Html.attribute("href") - PhoenixTest.visit(session.conn, path) + PhoenixTest.visit(session.conn, path) + end end def click_button(session, text) do @@ -62,14 +75,28 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Static do end def click_button(session, selector, text) do - if has_active_form?(session) do - session - |> validate_submit_buttons!(selector, text) - |> submit_active_form() - else - session - |> validate_submit_buttons!(selector, text) - |> single_button_form_submit(text) + cond do + has_active_form?(session) -> + session + |> validate_submit_buttons!(selector, text) + |> submit_active_form() + + data_attribute_form?(session, selector, text) -> + form = + session + |> render_html() + |> Query.find!(selector, text) + |> Html.DataAttributeForm.build() + |> Html.DataAttributeForm.validate!(selector, text) + + session.conn + |> dispatch(@endpoint, form.method, form.action, form.data) + |> maybe_redirect(session) + + true -> + session + |> validate_submit_buttons!(selector, text) + |> single_button_form_submit(text) end end @@ -94,6 +121,20 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Static do |> submit_active_form() end + defp data_attribute_form?(session, selector, text) do + session + |> render_html() + |> Query.find(selector, text) + |> case do + {:found, element} -> + method = Html.attribute(element, "data-method") + method != "" && method != nil + + _ -> + false + end + end + defp has_active_form?(session) do case PhoenixTest.Static.get_private(session, :active_form) do :not_found -> false diff --git a/test/phoenix_test/html/data_attribute_form_test.exs b/test/phoenix_test/html/data_attribute_form_test.exs new file mode 100644 index 00000000..88f7b22a --- /dev/null +++ b/test/phoenix_test/html/data_attribute_form_test.exs @@ -0,0 +1,101 @@ +defmodule PhoenixTest.Html.DataAttributeFormTest do + use ExUnit.Case, async: true + + alias PhoenixTest.Html.DataAttributeForm + alias PhoenixTest.Query + + describe "build/1" do + test "builds a form with method, action, csrf_token" do + element = + to_element(""" + + Delete + + """) + + form = DataAttributeForm.build(element) + + assert form.method == "put" + assert form.action == "/users/2" + assert form.csrf_token == "token" + end + + test "includes original element passed to build/1" do + element = + to_element(""" + + Delete + + """) + + form = DataAttributeForm.build(element) + + assert form.element == element + end + + test "creates form data of what would be hidden inputs in regular form" do + element = + to_element(""" + + Delete + + """) + + form = DataAttributeForm.build(element) + + assert form.data["_method"] == "put" + assert form.data["_csrf_token"] == "token" + end + end + + describe "validate!/1" do + test "raises an error if data-method is missing" do + element = + to_element(""" + + Delete + + """) + + assert_raise ArgumentError, ~r/missing: data-method/, fn -> + element + |> DataAttributeForm.build() + |> DataAttributeForm.validate!("a", "Delete") + end + end + + test "raises an error if data-to is missing" do + element = + to_element(""" + + Delete + + """) + + assert_raise ArgumentError, ~r/missing: data-to/, fn -> + element + |> DataAttributeForm.build() + |> DataAttributeForm.validate!("a", "Delete") + end + end + + test "raises an error if data-csrf is missing" do + element = + to_element(""" + + Delete + + """) + + assert_raise ArgumentError, ~r/missing: data-csrf/, fn -> + element + |> DataAttributeForm.build() + |> DataAttributeForm.validate!("a", "Delete") + end + end + end + + defp to_element(html) do + Query.find!(html, "a") + end +end diff --git a/test/phoenix_test/static_test.exs b/test/phoenix_test/static_test.exs index e0987b17..214748d0 100644 --- a/test/phoenix_test/static_test.exs +++ b/test/phoenix_test/static_test.exs @@ -2,6 +2,7 @@ defmodule PhoenixTest.StaticTest do use ExUnit.Case, async: true import PhoenixTest + import PhoenixTest.TestHelpers setup do %{conn: Phoenix.ConnTest.build_conn()} @@ -57,6 +58,51 @@ defmodule PhoenixTest.StaticTest do |> assert_has("h1", "Page 2") end + test "handles navigation to a LiveView", %{conn: conn} do + conn + |> visit("/page/index") + |> click_link("To LiveView!") + |> assert_has("h1", "LiveView main page") + end + + test "handles form submission via `data-method` & `data-to` attributes", %{conn: conn} do + conn + |> visit("/page/index") + |> click_link("Data-method Delete") + |> assert_has("h1", "Record deleted") + end + + test "raises error if trying to submit via `data-` attributes but incomplete", %{conn: conn} do + msg = + """ + Tried submitting form via `data-method` but some data attributes are + missing. + + I expected "a" with text "Incomplete data-method Delete" to include + data-method, data-to, and data-csrf. + + I found: + + + Incomplete data-method Delete + + + It seems these are missing: data-to, data-csrf. + + NOTE: `data-method` form submissions happen through JavaScript. Tests + emulate that, but be sure to verify you're including Phoenix.HTML.js! + + See: https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library + """ + |> ignore_whitespace() + + assert_raise ArgumentError, msg, fn -> + conn + |> visit("/page/index") + |> click_link("Incomplete data-method Delete") + end + end + test "raises error when there are multiple links with same text", %{conn: conn} do assert_raise ArgumentError, ~r/Found more than one element with selector/, fn -> conn @@ -65,13 +111,6 @@ defmodule PhoenixTest.StaticTest do end end - test "handles navigation to a LiveView", %{conn: conn} do - conn - |> visit("/page/index") - |> click_link("To LiveView!") - |> assert_has("h1", "LiveView main page") - end - test "raises an error when link element can't be found with given text", %{conn: conn} do assert_raise ArgumentError, ~r/Could not find element with selector/, fn -> conn @@ -125,6 +164,44 @@ defmodule PhoenixTest.StaticTest do |> assert_has("h1", "LiveView main page") end + test "handles form submission via `data-method` & `data-to` attributes", %{conn: conn} do + conn + |> visit("/page/index") + |> click_button("Data-method Delete") + |> assert_has("h1", "Record deleted") + end + + test "raises error if trying to submit via `data-` attributes but incomplete", %{conn: conn} do + msg = + """ + Tried submitting form via `data-method` but some data attributes are + missing. + + I expected "button" with text "Incomplete data-method Delete" to include + data-method, data-to, and data-csrf. + + I found: + + + + It seems these are missing: data-to, data-csrf. + + NOTE: `data-method` form submissions happen through JavaScript. Tests + emulate that, but be sure to verify you're including Phoenix.HTML.js! + + See: https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library + """ + |> ignore_whitespace() + + assert_raise ArgumentError, msg, fn -> + conn + |> visit("/page/index") + |> click_button("Incomplete data-method Delete") + end + end + test "raises an error when there are no buttons on page", %{conn: conn} do assert_raise ArgumentError, ~r/Could not find an element with given selector/, fn -> conn diff --git a/test/support/page_view.ex b/test/support/page_view.ex index ddfac295..5d16cfb0 100644 --- a/test/support/page_view.ex +++ b/test/support/page_view.ex @@ -41,6 +41,23 @@ defmodule PhoenixTest.PageView do Has extra space + Incomplete data-method Delete + + + Data-method Delete + + + + + +