-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
What changed? ============= Phoenix allows form submissions through data attributes: `data-to`, `data-method` and `data-csrf`. That comes from the `Phoenix.HTML` library's little [JS snippet]: ```js function handleClick(element, targetModifierKey) { var to = element.getAttribute("data-to"), method = buildHiddenInput("_method", element.getAttribute("data-method")), csrf = buildHiddenInput("_csrf_token", element.getAttribute("data-csrf")), form = document.createElement("form"), submit = document.createElement("input"), target = element.getAttribute("target"); form.method = (element.getAttribute("data-method") === "get") ? "get" : "post"; form.action = to; form.style.display = "none"; if (target) form.target = target; else if (targetModifierKey) form.target = "_blank"; form.appendChild(csrf); form.appendChild(method); document.body.appendChild(form); // Insert a button and click it instead of using `form.submit` // because the `submit` function does not emit a `submit` event. submit.type = "submit"; form.appendChild(submit); submit.click(); } ``` [JS snippet]: https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library The core logic that matters to us is as follows: - It uses the `data-to` as the action. - It builds two hidden inputs for "_method" and "_csrf_token". In order for us to handle that, we reproduce some of the same logic. We pull the `to`, `method`, and `csrf_token` values from the element. Thus, our `Static` logic is now able to handle these types of form submissions. There's an open question whether or not we should support those in LiveView. It seems like an odd use case. Most people would use a regular form submission (if they don't want to use LiveView) or a LiveView form. So, for now, we don't introduce the same logic in `Live`. NOTE ---- Unlike other code, supporting this means users can have tests that pass when production is broken!!! (for example, if they forget to put the Phoenix.HTML.js in the app). That's not ideal, but it seems like a worthwhile trade-off while Phoenix supports those forms as first-class behavior. Otherwise, we cannot test those types of form submissions -- which are still standard enough that Phoenix generators come with them out of the box.
- Loading branch information
Showing
5 changed files
with
312 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(""" | ||
<a data-method="put" data-to="/users/2" data-csrf="token"> | ||
Delete | ||
</a> | ||
""") | ||
|
||
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(""" | ||
<a data-method="put" data-to="/users/2" data-csrf="token"> | ||
Delete | ||
</a> | ||
""") | ||
|
||
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(""" | ||
<a data-method="put" data-to="/users/2" data-csrf="token"> | ||
Delete | ||
</a> | ||
""") | ||
|
||
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(""" | ||
<a data-to="/users/2" data-csrf="token"> | ||
Delete | ||
</a> | ||
""") | ||
|
||
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(""" | ||
<a data-method="put" data-csrf="token"> | ||
Delete | ||
</a> | ||
""") | ||
|
||
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(""" | ||
<a data-method="put" data-to="/users/2"> | ||
Delete | ||
</a> | ||
""") | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters