Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/phoenix_test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,12 @@ defmodule PhoenixTest do

This can be followed by a `click_button/3` or `submit/1` to submit the form.

If the input has a `phx-change` attribute, the `phx-change` will be triggered.
For this to work, the input needs to be wrapped with a `<form>` element
(just like a regular LiveView).

If the form is a LiveView form, and if the form has a `phx-change` attribute
defined, `fill_in/3` will trigger the `phx-change` event.
defined, `fill_in/3` will trigger the `phx-change` event on the form.

## Options

Expand Down
2 changes: 2 additions & 0 deletions lib/phoenix_test/element/field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ defmodule PhoenixTest.Element.Field do

def phx_value?(field), do: LiveViewBindings.phx_value?(field.parsed)

def phx_change?(field), do: LiveViewBindings.phx_change?(field.parsed)

def belongs_to_form?(field, html) do
case Query.find_ancestor(html, "form", field.selector) do
{:found, _} -> true
Expand Down
55 changes: 44 additions & 11 deletions lib/phoenix_test/live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -404,28 +404,61 @@ defmodule PhoenixTest.Live do
session =
Map.update!(session, :active_form, fn active_form ->
if active_form.selector == form.selector do
ActiveForm.add_form_data(session.active_form, field)
ActiveForm.add_form_data(active_form, field)
else
[id: form.id, selector: form.selector]
|> ActiveForm.new()
|> ActiveForm.add_form_data(field)
end
end)

if Form.phx_change?(form) do
active_form = session.active_form
data_to_submit = FormData.merge(form.form_data, active_form.form_data)
additional_data = %{"_target" => field.name}
maybe_trigger_phx_change(session, form, field)
end

session.view
|> form(form.selector, FormPayload.new(data_to_submit))
|> render_change(additional_data)
|> maybe_redirect(session)
else
session
defp maybe_trigger_phx_change(session, form, field) do
cond do
Field.phx_change?(field) ->
trigger_input_phx_change(session, form, field)

Form.phx_change?(form) ->
trigger_form_phx_change(session, form, field)

true ->
session
end
end

defp trigger_input_phx_change(session, form, field) do
data_to_submit =
session
|> merged_form_data(form)
|> FormData.filter(&(&1.name == field.name))

payload =
data_to_submit
|> FormPayload.new()
|> Map.put("_target", field.name)

session.view
|> element(scope_selector(field.selector, session.within))
|> render_change(payload)
|> maybe_redirect(session)
end

defp trigger_form_phx_change(session, form, field) do
data_to_submit = merged_form_data(session, form)
additional_data = %{"_target" => field.name}

session.view
|> form(form.selector, FormPayload.new(data_to_submit))
|> render_change(additional_data)
|> maybe_redirect(session)
end

defp merged_form_data(session, form) do
FormData.merge(form.form_data, session.active_form.form_data)
end

def submit(session) do
active_form = session.active_form

Expand Down
6 changes: 6 additions & 0 deletions lib/phoenix_test/live_view_bindings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ defmodule PhoenixTest.LiveViewBindings do
|> valid_event_or_js_command?()
end

def phx_change?(parsed_element) do
parsed_element
|> Html.attribute("phx-change")
|> valid_event_or_js_command?()
end

def phx_value?(parsed_element) do
cond do
any_phx_value_attributes?(parsed_element) -> true
Expand Down
32 changes: 32 additions & 0 deletions test/phoenix_test/live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1401,5 +1401,37 @@ defmodule PhoenixTest.LiveTest do
|> click_button("Async redirect!")
|> refute_has("h2", text: "Where we test LiveView's async behavior", timeout: 250)
end

test "check triggers phx-change on the input if it is defined", %{conn: conn} do
conn
|> visit("/live/index")
|> check("Checkbox 1")
|> assert_has("#input-with-change-result", text: "_target: checkbox-with-change")
|> assert_has("#input-with-change-result", text: "value: Checkbox 1")
end

test "choose triggers phx-change on the input if it is defined", %{conn: conn} do
conn
|> visit("/live/index")
|> choose("Option 1")
|> assert_has("#input-with-change-result", text: "_target: radio-with-change")
|> assert_has("#input-with-change-result", text: "value: Option 1")
end

test "fill_in triggers phx-change on the input if it is defined", %{conn: conn} do
conn
|> visit("/live/index")
|> fill_in("Input with change", with: "a test value")
|> assert_has("#input-with-change-result", text: "_target: input-with-change")
|> assert_has("#input-with-change-result", text: "value: a test value")
end

test "select triggers phx-change on the input if it is defined", %{conn: conn} do
conn
|> visit("/live/index")
|> select("Select with change", option: "Option 1")
|> assert_has("#input-with-change-result", text: "_target: select-with-change")
|> assert_has("#input-with-change-result", text: "value: Option 1")
end
end
end
66 changes: 66 additions & 0 deletions test/support/web_app/index_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,64 @@ defmodule PhoenixTest.WebApp.IndexLive do
{if(@checked_keys["def"], do: "Checked", else: "Unchecked")}
</span>
</div>

<form phx-change="dummy-change">
<label for="input-with-change">Input with change</label>
<input
id="input-with-change"
name="input-with-change"
phx-change="input-changed"
/>

<!-- Radio inputs -->
<input
type="radio"
id="radio-input-choice-1"
name="radio-with-change"
value="Option 1"
phx-change="input-changed"
/>
<label for="radio-input-choice-1">Option 1</label>

<input
type="radio"
id="radio-input-choice-2"
name="radio-with-change"
value="Option 2"
phx-change="input-changed"
/>
<label for="radio-input-choice-2">Option 2</label>

<!-- Checkboxes -->
<label for="checkbox-input-checkbox-1">Checkbox 1</label>
<input
id="checkbox-input-checkbox-1"
type="checkbox"
name="checkbox-with-change"
value="Checkbox 1"
phx-change="input-changed"
/>

<label for="checkbox-input-checkbox-2">Checkbox 2</label>
<input
id="checkbox-input-checkbox-2"
type="checkbox"
name="checkbox-with-change"
value="Checkbox 2"
phx-change="input-changed"
/>

<!-- Select -->
<label for="select-with-change">Select with change</label>
<select id="select-with-change" name="select-with-change" phx-change="input-changed">
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
</select>
</form>

<div :if={@input_change_data} id="input-with-change-result">
_target: {@input_change_data.target} value: {@input_change_data.value}
</div>
"""
end

Expand Down Expand Up @@ -648,6 +706,7 @@ defmodule PhoenixTest.WebApp.IndexLive do
|> assign(:trigger_multiple_submit, false)
|> assign(:redirect_and_trigger_submit, false)
|> assign(:upload_change_triggered, false)
|> assign(:input_change_data, nil)
|> allow_upload(:avatar, accept: ~w(.jpg .jpeg))
|> allow_upload(:avatar_2, accept: ~w(.jpg .jpeg))
|> allow_upload(:avatar_3, accept: ~w(.jpg .jpeg))
Expand Down Expand Up @@ -874,6 +933,13 @@ defmodule PhoenixTest.WebApp.IndexLive do
}
end

def handle_event("input-changed", params, socket) do
# disregard the input name. We are only interested in the event target and the sent value
[{_, target}, {_input_name, value} | _rest] = Map.to_list(params)

{:noreply, assign(socket, :input_change_data, %{target: target, value: value})}
end

def handle_event("toggle-checkbox-phx-value", %{"id" => id}, socket) do
checked_keys = Map.update(socket.assigns.checked_keys, id, true, &(not &1))

Expand Down