forked from phoenixframework/phoenix_live_view
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
215 additions
and
1 deletion.
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
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
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,38 @@ | ||
const { test, expect } = require("../../test-fixtures"); | ||
const { syncLV } = require("../../utils"); | ||
|
||
const selectOptions = (locator) => locator.evaluateAll(list => list.map(option => option.value)); | ||
|
||
test("select is properly cleared on submit", async ({ page }) => { | ||
await page.goto("/issues/2787"); | ||
await syncLV(page); | ||
|
||
const select1 = page.locator("#demo_select1"); | ||
const select2 = page.locator("#demo_select2"); | ||
|
||
// at the beginning, both selects are empty | ||
await expect(select1).toHaveValue(""); | ||
await expect(await selectOptions(select1.locator("option"))).toEqual(["", "greetings", "goodbyes"]); | ||
await expect(select2).toHaveValue(""); | ||
await expect(await selectOptions(select2.locator("option"))).toEqual([""]); | ||
|
||
// now we select greetings in the first select | ||
await select1.selectOption("greetings"); | ||
await syncLV(page); | ||
// now the second select should have some greeting options | ||
await expect(await selectOptions(select2.locator("option"))).toEqual(["", "hello", "hallo", "hei"]); | ||
await select2.selectOption("hei"); | ||
await syncLV(page); | ||
|
||
// now we submit the form | ||
await page.locator("button").click(); | ||
|
||
// now, both selects should be empty again (this was the bug in #2787) | ||
await expect(select1).toHaveValue(""); | ||
await expect(select2).toHaveValue(""); | ||
|
||
// now we select goodbyes in the first select | ||
await select1.selectOption("goodbyes"); | ||
await syncLV(page); | ||
await expect(await selectOptions(select2.locator("option"))).toEqual(["", "goodbye", "auf wiedersehen", "ha det bra"]); | ||
}); |
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,170 @@ | ||
defmodule Phoenix.LiveViewTest.E2E.Issue2787Live do | ||
use Phoenix.LiveView | ||
|
||
# https://github.com/phoenixframework/phoenix_live_view/issues/2787 | ||
|
||
@greetings ["hello", "hallo", "hei"] | ||
@goodbyes ["goodbye", "auf wiedersehen", "ha det bra"] | ||
|
||
@impl Phoenix.LiveView | ||
def mount(_params, _session, socket) do | ||
{:ok, | ||
assign(socket, | ||
form: to_form(changeset(%{}), as: :demo), | ||
select1_opts: ["greetings", "goodbyes"], | ||
select2_opts: [] | ||
)} | ||
end | ||
|
||
@types %{ | ||
select1: :string, | ||
select2: :string, | ||
dummy: :string | ||
} | ||
|
||
def changeset(params) do | ||
Ecto.Changeset.cast({%{}, @types}, params, [:select1, :select2, :dummy]) | ||
Check warning on line 26 in test/support/e2e/issues/issue_2787.ex GitHub Actions / mix test (OTP 24.3 | Elixir 1.13.4)
Check warning on line 26 in test/support/e2e/issues/issue_2787.ex GitHub Actions / mix test (OTP 25.3 | Elixir 1.15.4)
|
||
end | ||
|
||
@impl Phoenix.LiveView | ||
def handle_event("updated", %{"demo" => demo_params}, socket) do | ||
select2_opts = | ||
case Map.get(demo_params, "select1") do | ||
"greetings" -> @greetings | ||
"goodbyes" -> @goodbyes | ||
_ -> [] | ||
end | ||
|
||
# Ideally select2 gets reset when select1 updates but we'll leave it off | ||
# for simplicity | ||
|
||
{:noreply, | ||
assign(socket, form: to_form(changeset(demo_params), as: :demo), select2_opts: select2_opts)} | ||
end | ||
|
||
def handle_event("submitted", %{"demo" => _demo_params}, socket) do | ||
{:noreply, | ||
assign(socket, | ||
form: to_form(changeset(%{}), as: :demo), | ||
select2_opts: [] | ||
)} | ||
end | ||
|
||
@impl Phoenix.LiveView | ||
def render(assigns) do | ||
~H""" | ||
<script src="https://cdn.tailwindcss.com/3.4.3"></script> | ||
<div class="p-20"> | ||
<.form for={@form} phx-change="updated" phx-submit="submitted" class="space-y-4"> | ||
<.input | ||
type="select" | ||
field={@form[:select1]} | ||
label="select1" | ||
prompt="Select" | ||
options={@select1_opts} | ||
/> | ||
<.input | ||
type="select" | ||
field={@form[:select2]} | ||
label="select2" | ||
prompt="Select" | ||
options={@select2_opts} | ||
/> | ||
<.input type="text" field={@form[:dummy]} label="Some text" /> | ||
<button class="text-sm border bg-zinc-200" type="submit">Submit</button> | ||
</.form> | ||
</div> | ||
""" | ||
end | ||
|
||
attr :for, :string, default: nil | ||
slot(:inner_block, required: true) | ||
|
||
def label(assigns) do | ||
~H""" | ||
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800"> | ||
<%= render_slot(@inner_block) %> | ||
</label> | ||
""" | ||
end | ||
|
||
### | ||
# Input components copied and adjusted from generated core_components | ||
|
||
attr :id, :any, default: nil | ||
attr :name, :any | ||
attr :label, :string, default: nil | ||
attr :value, :any | ||
|
||
attr :type, :string, | ||
default: "text", | ||
values: ~w(checkbox color date datetime-local email file month number password | ||
range search select tel text textarea time url week) | ||
|
||
attr :field, Phoenix.HTML.FormField | ||
|
||
attr :errors, :list, default: [] | ||
attr :checked, :boolean | ||
attr :prompt, :string | ||
attr :options, :list | ||
attr :multiple, :boolean, default: false | ||
|
||
attr :rest, :global, | ||
include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength | ||
multiple pattern placeholder readonly required rows size step) | ||
|
||
slot(:inner_block) | ||
|
||
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do | ||
errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] | ||
|
||
assigns | ||
|> assign(field: nil, id: assigns.id || field.id) | ||
|> assign(:errors, errors) | ||
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end) | ||
|> assign_new(:value, fn -> field.value end) | ||
|> input() | ||
end | ||
|
||
def input(%{type: "select"} = assigns) do | ||
~H""" | ||
<div> | ||
<.label for={@id}><%= @label %></.label> | ||
<select | ||
id={@id} | ||
name={@name} | ||
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm" | ||
multiple={@multiple} | ||
{@rest} | ||
> | ||
<option :if={@prompt} value=""><%= @prompt %></option> | ||
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %> | ||
</select> | ||
</div> | ||
""" | ||
end | ||
|
||
# All other inputs text, datetime-local, url, password, etc. are handled here... | ||
def input(assigns) do | ||
~H""" | ||
<div> | ||
<.label for={@id}><%= @label %></.label> | ||
<input | ||
type={@type} | ||
name={@name} | ||
id={@id} | ||
value={Phoenix.HTML.Form.normalize_value(@type, @value)} | ||
class={[ | ||
"mt-2 px-2 block w-full rounded-lg text-zinc-900 border focus:ring-0 sm:text-sm sm:leading-6", | ||
@errors == [] && "border-zinc-300 focus:border-zinc-400", | ||
@errors != [] && "border-rose-400 focus:border-rose-400" | ||
]} | ||
{@rest} | ||
/> | ||
</div> | ||
""" | ||
end | ||
end |