Repository: woylie/doggo
Pull request #453 (2025-02-22 01:59:29)

fix/selected option
- **fix: escape select option values**
- **bump version**



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7bb330c..be56e93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 ## Unreleased
 
+## [0.10.4] - 2025-02-22
+
+### Fixed
+
+- Escape select option values in `field` component.
+
 ## [0.10.3] - 2025-02-11
 
 ### Changed
diff --git a/README.md b/README.md
index cc407f6..5106cee 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ The package can be installed by adding `doggo` to your list of dependencies in
 ```elixir
 def deps do
   [
-    {:doggo, "~> 0.10.3"}
+    {:doggo, "~> 0.10.4"}
   ]
 end
 ```
diff --git a/lib/doggo/components/field.ex b/lib/doggo/components/field.ex
index 3f2a5d6..e040715 100644
--- a/lib/doggo/components/field.ex
+++ b/lib/doggo/components/field.ex
@@ -496,6 +496,13 @@ defmodule Doggo.Components.Field do
   end
 
   def render(%{type: "select"} = assigns) do
+    assigns =
+      assign(
+        assigns,
+        :value,
+        assigns[:value] |> List.wrap() |> Enum.map(&Phoenix.HTML.html_escape/1)
+      )
+
     ~H"""
     <div class={@class}>
       <.label
@@ -524,9 +531,8 @@ defmodule Doggo.Components.Field do
           <option :if={@prompt} value="">{@prompt}</option>
           <.option
             :for={option <- @options}
-            selected_value={@value}
+            selected_values={@value}
             option={option}
-            multiple={@multiple}
           />
         </select>
       </div>
@@ -837,6 +843,9 @@ defmodule Doggo.Components.Field do
     ~H""
   end
 
+  attr :option, :any, required: true
+  attr :selected_values, :list, default: []
+
   defp option(%{option: :hr} = assigns) do
     ~H"""
     <hr />
@@ -855,8 +864,7 @@ defmodule Doggo.Components.Field do
       <.option
         :for={option <- @options}
         option={option}
-        selected_value={@selected_value}
-        multiple={@multiple}
+        selected_values={@selected_values}
       />
     </optgroup>
     """
@@ -866,35 +874,21 @@ defmodule Doggo.Components.Field do
     assigns =
       assigns
       |> assign(:key, key)
-      |> assign(:value, value)
-      |> assign_new(:selected_value, fn -> nil end)
-      |> assign_new(:multiple, fn -> nil end)
+      |> assign(:value, Phoenix.HTML.html_escape(value))
 
     ~H"""
-    <option
-      value={@value}
-      selected={
-        (@multiple && @value in (@selected_value || [])) ||
-          @value == @selected_value
-      }
-    >
+    <option value={@value} selected={@value in @selected_values}>
       {@key}
     </option>
     """
   end
 
   defp option(%{option: options} = assigns) when is_map(options) do
-    assigns =
-      assigns
-      |> assign_new(:selected_value, fn -> nil end)
-      |> assign_new(:multiple, fn -> nil end)
-
     ~H"""
     <.option
       :for={{key, value} <- @option}
       option={{key, value}}
-      selected_value={@selected_value}
-      multiple={@multiple}
+      selected_values={@selected_values}
     />
     """
   end
@@ -904,15 +898,9 @@ defmodule Doggo.Components.Field do
       assigns
       |> assign(:key, key)
       |> assign(:value, value)
-      |> assign_new(:selected_value, fn -> nil end)
-      |> assign_new(:multiple, fn -> nil end)
 
     ~H"""
-    <.option
-      option={{@key, @value}}
-      selected_value={@selected_value}
-      multiple={@multiple}
-    />
+    <.option option={{@key, @value}} selected_values={@selected_values} />
     """
   end
 
@@ -931,17 +919,8 @@ defmodule Doggo.Components.Field do
   end
 
   defp option(%{option: _key_and_value} = assigns) do
-    assigns =
-      assigns
-      |> assign_new(:selected_value, fn -> nil end)
-      |> assign_new(:multiple, fn -> nil end)
-
     ~H"""
-    <.option
-      option={{@option, @option}}
-      selected_value={@selected_value}
-      multiple={@multiple}
-    />
+    <.option option={{@option, @option}} selected_values={@selected_values} />
     """
   end
 
diff --git a/mix.exs b/mix.exs
index 723668e..6246f73 100644
--- a/mix.exs
+++ b/mix.exs
@@ -2,7 +2,7 @@ defmodule Doggo.MixProject do
   use Mix.Project
 
   @source_url "https://github.com/woylie/doggo"
-  @version "0.10.3"
+  @version "0.10.4"
 
   def project do
     [


Test patch:
diff --git a/test/doggo/components/field_test.exs b/test/doggo/components/field_test.exs
index 0f45c87..5fd0c48 100644
--- a/test/doggo/components/field_test.exs
+++ b/test/doggo/components/field_test.exs
@@ -351,6 +351,33 @@ defmodule Doggo.Components.FieldTest do
       assert [_, {"hr", [], []}, _] = options
     end
 
+    test "escapes select options" do
+      assigns = %{form: to_form(%{"animals" => "dog"})}
+
+      html =
+        parse_heex(~H"""
+        <.form for={@form}>
+          <TestComponents.field
+            field={@form[:animals]}
+            label="Animals"
+            type="select"
+            options={[{"Dog", :dog}, :hr, {"Cat", :cat}]}
+          >
+            <:description>Which animals?</:description>
+          </TestComponents.field>
+        </.form>
+        """)
+
+      assert attribute(html, "option:first-child", "value") == "dog"
+      assert attribute(html, "option:first-child", "selected") == "selected"
+      assert attribute(html, "option:last-child", "value") == "cat"
+      assert attribute(html, "option:last-child", "selected") == nil
+
+      # Check if the <hr /> is the middle option
+      options = html |> Floki.find("select") |> hd() |> elem(2)
+      assert [_, {"hr", [], []}, _] = options
+    end
+
     test "with multiple select" do
       assigns = %{form: to_form(%{})}