diff --git a/lib/mv_web/components/layouts/navbar.ex b/lib/mv_web/components/layouts/navbar.ex index 7ff7f25..acbed90 100644 --- a/lib/mv_web/components/layouts/navbar.ex +++ b/lib/mv_web/components/layouts/navbar.ex @@ -23,7 +23,7 @@ defmodule MvWeb.Layouts.Navbar do {@club_name} diff --git a/lib/mv_web/live/custom_field_live/form.ex b/lib/mv_web/live/custom_field_live/form.ex deleted file mode 100644 index 99317a9..0000000 --- a/lib/mv_web/live/custom_field_live/form.ex +++ /dev/null @@ -1,142 +0,0 @@ -defmodule MvWeb.CustomFieldLive.Form do - @moduledoc """ - LiveView form for creating and editing custom fields (admin). - - ## Features - - Create new custom field definitions - - Edit existing custom fields - - Select value type from supported types - - Set immutable and required flags - - Real-time validation - - ## Form Fields - **Required:** - - name - Unique identifier (e.g., "phone_mobile", "emergency_contact") - - value_type - Data type (:string, :integer, :boolean, :date, :email) - - **Optional:** - - description - Human-readable explanation - - immutable - If true, values cannot be changed after creation (default: false) - - required - If true, all members must have this custom field (default: false) - - show_in_overview - If true, this custom field will be displayed in the member overview table (default: true) - - ## Value Type Selection - - `:string` - Text data (unlimited length) - - `:integer` - Numeric data - - `:boolean` - True/false flags - - `:date` - Date values - - `:email` - Validated email addresses - - ## Events - - `validate` - Real-time form validation - - `save` - Submit form (create or update custom field) - - ## Security - Custom field management is restricted to admin users. - """ - use MvWeb, :live_view - - @impl true - def render(assigns) do - ~H""" - - <.header> - {@page_title} - <:subtitle> - {gettext("Use this form to manage custom_field records in your database.")} - - - - <.form for={@form} id="custom_field-form" phx-change="validate" phx-submit="save"> - <.input field={@form[:name]} type="text" label={gettext("Name")} /> - - <.input - field={@form[:value_type]} - type="select" - label={gettext("Value type")} - options={ - Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of] - } - /> - <.input field={@form[:description]} type="text" label={gettext("Description")} /> - <.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} /> - <.input field={@form[:required]} type="checkbox" label={gettext("Required")} /> - <.input field={@form[:show_in_overview]} type="checkbox" label={gettext("Show in overview")} /> - - <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save Custom field")} - - <.button navigate={return_path(@return_to, @custom_field)}>{gettext("Cancel")} - - - """ - end - - @impl true - def mount(params, _session, socket) do - custom_field = - case params["id"] do - nil -> nil - id -> Ash.get!(Mv.Membership.CustomField, id) - end - - action = if is_nil(custom_field), do: "New", else: "Edit" - page_title = action <> " " <> "Custom field" - - {:ok, - socket - |> assign(:return_to, return_to(params["return_to"])) - |> assign(custom_field: custom_field) - |> assign(:page_title, page_title) - |> assign_form()} - end - - defp return_to("show"), do: "show" - defp return_to(_), do: "index" - - @impl true - def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do - {:noreply, - assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))} - end - - def handle_event("save", %{"custom_field" => custom_field_params}, socket) do - case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do - {:ok, custom_field} -> - notify_parent({:saved, custom_field}) - - action = - case socket.assigns.form.source.type do - :create -> gettext("create") - :update -> gettext("update") - other -> to_string(other) - end - - socket = - socket - |> put_flash(:info, gettext("Custom field %{action} successfully", action: action)) - |> push_navigate(to: return_path(socket.assigns.return_to, custom_field)) - - {:noreply, socket} - - {:error, form} -> - {:noreply, assign(socket, form: form)} - end - end - - defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) - - defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do - form = - if custom_field do - AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field") - else - AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field") - end - - assign(socket, form: to_form(form)) - end - - defp return_path("index", _custom_field), do: ~p"/custom_fields" - defp return_path("show", custom_field), do: ~p"/custom_fields/#{custom_field.id}" -end diff --git a/lib/mv_web/live/custom_field_live/form_component.ex b/lib/mv_web/live/custom_field_live/form_component.ex new file mode 100644 index 0000000..c0b731c --- /dev/null +++ b/lib/mv_web/live/custom_field_live/form_component.ex @@ -0,0 +1,110 @@ +defmodule MvWeb.CustomFieldLive.FormComponent do + @moduledoc """ + LiveComponent form for creating and editing custom fields (embedded in settings). + + ## Features + - Create new custom field definitions + - Edit existing custom fields + - Select value type from supported types + - Set immutable and required flags + - Real-time validation + + ## Props + - `custom_field` - The custom field to edit (nil for new) + - `on_save` - Callback function to call when form is saved + - `on_cancel` - Callback function to call when form is cancelled + """ + use MvWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+
+

+ {if @custom_field, do: gettext("Edit Custom Field"), else: gettext("New Custom Field")} +

+ + <.form + for={@form} + id={@id <> "-form"} + phx-change="validate" + phx-submit="save" + phx-target={@myself} + > + <.input field={@form[:name]} type="text" label={gettext("Name")} /> + + <.input + field={@form[:value_type]} + type="select" + label={gettext("Value type")} + options={ + Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of] + } + /> + <.input field={@form[:description]} type="text" label={gettext("Description")} /> + <.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} /> + <.input field={@form[:required]} type="checkbox" label={gettext("Required")} /> + <.input + field={@form[:show_in_overview]} + type="checkbox" + label={gettext("Show in overview")} + /> + +
+ <.button type="button" phx-click="cancel" phx-target={@myself}> + {gettext("Cancel")} + + <.button phx-disable-with={gettext("Saving...")} variant="primary"> + {gettext("Save Custom field")} + +
+ +
+
+ """ + end + + @impl true + def update(assigns, socket) do + {:ok, + socket + |> assign(assigns) + |> assign_form()} + end + + @impl true + def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do + {:noreply, + assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))} + end + + @impl true + def handle_event("save", %{"custom_field" => custom_field_params}, socket) do + case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do + {:ok, custom_field} -> + socket.assigns.on_save.(custom_field) + {:noreply, socket} + + {:error, form} -> + {:noreply, assign(socket, form: form)} + end + end + + @impl true + def handle_event("cancel", _params, socket) do + socket.assigns.on_cancel.() + {:noreply, socket} + end + + defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do + form = + if custom_field do + AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field") + else + AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field") + end + + assign(socket, form: to_form(form)) + end +end diff --git a/lib/mv_web/live/custom_field_live/index.ex b/lib/mv_web/live/custom_field_live/index_component.ex similarity index 55% rename from lib/mv_web/live/custom_field_live/index.ex rename to lib/mv_web/live/custom_field_live/index_component.ex index f711323..a071b7a 100644 --- a/lib/mv_web/live/custom_field_live/index.ex +++ b/lib/mv_web/live/custom_field_live/index_component.ex @@ -1,6 +1,6 @@ -defmodule MvWeb.CustomFieldLive.Index do +defmodule MvWeb.CustomFieldLive.IndexComponent do @moduledoc """ - LiveView for managing custom field definitions (admin). + LiveComponent for managing custom field definitions (embedded in settings). ## Features - List all custom fields @@ -9,59 +9,75 @@ defmodule MvWeb.CustomFieldLive.Index do - Create new custom fields - Edit existing custom fields - Delete custom fields with confirmation (cascades to all custom field values) - - ## Displayed Information - - Name: Unique identifier for the custom field - - Value type: Data type constraint (string, integer, boolean, date, email) - - Description: Human-readable explanation - - Immutable: Whether custom field values can be changed after creation - - Required: Whether all members must have this custom field (future feature) - - ## Events - - `prepare_delete` - Opens deletion confirmation modal with member count - - `confirm_delete` - Executes deletion after slug verification - - `cancel_delete` - Cancels deletion and closes modal - - `update_slug_confirmation` - Updates slug input state - - ## Security - Custom field management is restricted to admin users. - Deletion requires entering the custom field's slug to prevent accidental deletions. """ - use MvWeb, :live_view + use MvWeb, :live_component @impl true def render(assigns) do ~H""" - +
<.header> - Listing Custom fields + {gettext("Custom Fields")} + <:subtitle> + {gettext("Manage custom field definitions for members.")} + <:actions> - <.button variant="primary" navigate={~p"/custom_fields/new"}> - <.icon name="hero-plus" /> New Custom field + <.button variant="primary" phx-click="new_custom_field" phx-target={@myself}> + <.icon name="hero-plus" /> {gettext("New Custom field")} + <%!-- Show form when creating or editing --%> +
+ <.live_component + module={MvWeb.CustomFieldLive.FormComponent} + id={@form_id} + custom_field={@editing_custom_field} + on_save={fn custom_field -> send(self(), {:custom_field_saved, custom_field}) end} + on_cancel={fn -> send(self(), :cancel_custom_field_form) end} + /> +
+ <.table id="custom_fields" rows={@streams.custom_fields} - row_click={fn {_id, custom_field} -> JS.navigate(~p"/custom_fields/#{custom_field}") end} + row_click={ + fn {_id, custom_field} -> + JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself) + end + } > - <:col :let={{_id, custom_field}} label="Name">{custom_field.name} + <:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name} - <:col :let={{_id, custom_field}} label="Description">{custom_field.description} + <:col :let={{_id, custom_field}} label={gettext("Value Type")}> + {custom_field.value_type} + + + <:col :let={{_id, custom_field}} label={gettext("Description")}> + {custom_field.description} + + + <:col :let={{_id, custom_field}} label={gettext("Show in Overview")}> + + {gettext("Yes")} + + + {gettext("No")} + + <:action :let={{_id, custom_field}}> -
- <.link navigate={~p"/custom_fields/#{custom_field}"}>Show -
- - <.link navigate={~p"/custom_fields/#{custom_field}/edit"}>Edit + <.link phx-click={ + JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself) + }> + {gettext("Edit")} + <:action :let={{_id, custom_field}}> - <.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id})}> - Delete + <.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)}> + {gettext("Delete")} @@ -100,7 +116,7 @@ defmodule MvWeb.CustomFieldLive.Index do
{@custom_field_to_delete.slug}
-
+
-
+ """ end @impl true - def mount(_params, _session, socket) do + def update(assigns, socket) do {:ok, socket - |> assign(:page_title, "Listing Custom fields") - |> assign(:show_delete_modal, false) - |> assign(:custom_field_to_delete, nil) - |> assign(:slug_confirmation, "") - |> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField))} + |> assign(assigns) + |> assign_new(:show_form, fn -> false end) + |> assign_new(:form_id, fn -> "custom-field-form-new" end) + |> assign_new(:editing_custom_field, fn -> nil end) + |> assign_new(:show_delete_modal, fn -> false end) + |> assign_new(:custom_field_to_delete, fn -> nil end) + |> assign_new(:slug_confirmation, fn -> "" end) + |> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField), reset: true)} + end + + @impl true + def handle_event("new_custom_field", _params, socket) do + {:noreply, + socket + |> assign(:show_form, true) + |> assign(:editing_custom_field, nil) + |> assign(:form_id, "custom-field-form-new")} + end + + @impl true + def handle_event("edit_custom_field", %{"id" => id}, socket) do + custom_field = Ash.get!(Mv.Membership.CustomField, id) + + {:noreply, + socket + |> assign(:show_form, true) + |> assign(:editing_custom_field, custom_field) + |> assign(:form_id, "custom-field-form-#{id}")} end @impl true @@ -165,26 +205,34 @@ defmodule MvWeb.CustomFieldLive.Index do custom_field = socket.assigns.custom_field_to_delete if socket.assigns.slug_confirmation == custom_field.slug do - # Delete the custom field (CASCADE will handle custom field values) case Ash.destroy(custom_field) do :ok -> + send(self(), {:custom_field_deleted, custom_field}) + {:noreply, socket - |> put_flash(:info, "Custom field deleted successfully") |> assign(:show_delete_modal, false) |> assign(:custom_field_to_delete, nil) |> assign(:slug_confirmation, "") |> stream_delete(:custom_fields, custom_field)} {:error, error} -> + send(self(), {:custom_field_delete_error, error}) + {:noreply, socket - |> put_flash(:error, "Failed to delete custom field: #{inspect(error)}")} + |> assign(:show_delete_modal, false) + |> assign(:custom_field_to_delete, nil) + |> assign(:slug_confirmation, "")} end else + send(self(), :custom_field_slug_mismatch) + {:noreply, socket - |> put_flash(:error, "Slug does not match. Deletion cancelled.")} + |> assign(:show_delete_modal, false) + |> assign(:custom_field_to_delete, nil) + |> assign(:slug_confirmation, "")} end end diff --git a/lib/mv_web/live/custom_field_live/show.ex b/lib/mv_web/live/custom_field_live/show.ex deleted file mode 100644 index 239b844..0000000 --- a/lib/mv_web/live/custom_field_live/show.ex +++ /dev/null @@ -1,75 +0,0 @@ -defmodule MvWeb.CustomFieldLive.Show do - @moduledoc """ - LiveView for displaying a single custom field's details (admin). - - ## Features - - Display custom field definition - - Show all attributes (name, value type, description, flags) - - Navigate to edit form - - Return to custom field list - - ## Displayed Information - - ID: Internal UUID identifier - - Slug: URL-friendly identifier (auto-generated, immutable) - - Name: Unique identifier - - Value type: Data type constraint - - Description: Optional explanation - - Immutable flag: Whether values can be changed - - Required flag: Whether all members need this custom field - - ## Navigation - - Back to custom field list - - Edit custom field - - ## Security - Custom field details are restricted to admin users. - """ - use MvWeb, :live_view - - @impl true - def render(assigns) do - ~H""" - - <.header> - Custom field {@custom_field.slug} - <:subtitle>This is a custom_field record from your database. - - <:actions> - <.button navigate={~p"/custom_fields"}> - <.icon name="hero-arrow-left" /> - - <.button - variant="primary" - navigate={~p"/custom_fields/#{@custom_field}/edit?return_to=show"} - > - <.icon name="hero-pencil-square" /> Edit Custom field - - - - - <.list> - <:item title="Id">{@custom_field.id} - - <:item title="Slug"> - {@custom_field.slug} -

- {gettext("Auto-generated identifier (immutable)")} -

- - - <:item title="Name">{@custom_field.name} - - <:item title="Description">{@custom_field.description} - -
- """ - end - - @impl true - def mount(%{"id" => id}, _session, socket) do - {:ok, - socket - |> assign(:page_title, "Show Custom field") - |> assign(:custom_field, Ash.get!(Mv.Membership.CustomField, id))} - end -end diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex index 0be4559..23cffe4 100644 --- a/lib/mv_web/live/global_settings_live.ex +++ b/lib/mv_web/live/global_settings_live.ex @@ -4,6 +4,7 @@ defmodule MvWeb.GlobalSettingsLive do ## Features - Edit the association/club name + - Manage custom fields - Real-time form validation - Success/error feedback @@ -28,8 +29,9 @@ defmodule MvWeb.GlobalSettingsLive do {:ok, socket - |> assign(:page_title, gettext("Club Settings")) + |> assign(:page_title, gettext("Settings")) |> assign(:settings, settings) + |> assign(:show_custom_field_form, false) |> assign_form()} end @@ -38,12 +40,16 @@ defmodule MvWeb.GlobalSettingsLive do ~H""" <.header> - {gettext("Club Settings")} + {gettext("Settings")} <:subtitle> {gettext("Manage global settings for the association.")} + <%!-- Club Settings Section --%> + <.header> + {gettext("Club Settings")} + <.form for={@form} id="settings-form" phx-change="validate" phx-submit="save"> <.input field={@form[:club_name]} @@ -56,6 +62,12 @@ defmodule MvWeb.GlobalSettingsLive do {gettext("Save Settings")} + + <%!-- Custom Fields Section --%> + <.live_component + module={MvWeb.CustomFieldLive.IndexComponent} + id="custom-fields-component" + /> """ end @@ -66,6 +78,7 @@ defmodule MvWeb.GlobalSettingsLive do assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, setting_params))} end + @impl true def handle_event("save", %{"setting" => setting_params}, socket) do case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do {:ok, updated_settings} -> @@ -82,6 +95,40 @@ defmodule MvWeb.GlobalSettingsLive do end end + @impl true + def handle_info({:custom_field_saved, _custom_field}, socket) do + {:noreply, + socket + |> assign(:show_custom_field_form, false) + |> put_flash(:info, gettext("Custom field saved successfully")) + |> push_event("refresh-custom-fields", %{})} + end + + @impl true + def handle_info(:cancel_custom_field_form, socket) do + {:noreply, assign(socket, :show_custom_field_form, false)} + end + + @impl true + def handle_info({:custom_field_deleted, _custom_field}, socket) do + {:noreply, put_flash(socket, :info, gettext("Custom field deleted successfully"))} + end + + @impl true + def handle_info({:custom_field_delete_error, error}, socket) do + {:noreply, + put_flash( + socket, + :error, + gettext("Failed to delete custom field: %{error}", error: inspect(error)) + )} + end + + @impl true + def handle_info(:custom_field_slug_mismatch, socket) do + {:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))} + end + defp assign_form(%{assigns: %{settings: settings}} = socket) do form = AshPhoenix.Form.for_update( diff --git a/lib/mv_web/router.ex b/lib/mv_web/router.ex index 09a2792..8b1b0e6 100644 --- a/lib/mv_web/router.ex +++ b/lib/mv_web/router.ex @@ -55,12 +55,6 @@ defmodule MvWeb.Router do live "/members/:id", MemberLive.Show, :show live "/members/:id/show/edit", MemberLive.Show, :edit - live "/custom_fields", CustomFieldLive.Index, :index - live "/custom_fields/new", CustomFieldLive.Form, :new - live "/custom_fields/:id/edit", CustomFieldLive.Form, :edit - live "/custom_fields/:id", CustomFieldLive.Show, :show - live "/custom_fields/:id/show/edit", CustomFieldLive.Show, :edit - live "/custom_field_values", CustomFieldValueLive.Index, :index live "/custom_field_values/new", CustomFieldValueLive.Form, :new live "/custom_field_values/:id/edit", CustomFieldValueLive.Form, :edit diff --git a/test/membership/member_field_visibility_test.exs b/test/membership/member_field_visibility_test.exs deleted file mode 100644 index 9963169..0000000 --- a/test/membership/member_field_visibility_test.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Mv.Membership.MemberFieldVisibilityTest do - @moduledoc """ - Tests for member field visibility configuration. - - Tests cover: - - Member fields are visible by default (show_in_overview: true) - - Member fields can be hidden (show_in_overview: false) - - Checking if a specific field is visible - - Configuration is stored in Settings resource - """ - use Mv.DataCase, async: true - - alias Mv.Membership.Member -end diff --git a/test/mv_web/live/custom_field_live/deletion_test.exs b/test/mv_web/live/custom_field_live/deletion_test.exs index f0317e0..322cf38 100644 --- a/test/mv_web/live/custom_field_live/deletion_test.exs +++ b/test/mv_web/live/custom_field_live/deletion_test.exs @@ -1,6 +1,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do @moduledoc """ - Tests for CustomFieldLive.Index deletion modal and slug confirmation. + Tests for CustomFieldLive.IndexComponent deletion modal and slug confirmation. + Tests the custom field management component embedded in the settings page. Tests cover: - Opening deletion confirmation modal @@ -39,11 +40,11 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do # Create custom field value create_custom_field_value(member, custom_field, "test") - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") - # Click delete button + # Click delete button - find the delete link within the component view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() # Modal should be visible @@ -65,10 +66,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do create_custom_field_value(member1, custom_field, "test1") create_custom_field_value(member2, custom_field, "test2") - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() # Should show plural form @@ -78,10 +79,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do test "shows 0 members for custom field without values", %{conn: conn} do {:ok, _custom_field} = create_custom_field("test_field", :string) - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() # Should show 0 members @@ -93,15 +94,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do test "updates confirmation state when typing", %{conn: conn} do {:ok, custom_field} = create_custom_field("test_field", :string) - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() - # Type in slug input + # Type in slug input - use element to find the form with phx-target view - |> render_change("update_slug_confirmation", %{"slug" => custom_field.slug}) + |> element("#delete-custom-field-modal form") + |> render_change(%{"slug" => custom_field.slug}) # Confirm button should be enabled now (no disabled attribute) html = render(view) @@ -111,15 +113,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do test "delete button is disabled when slug doesn't match", %{conn: conn} do {:ok, _custom_field} = create_custom_field("test_field", :string) - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() - # Type wrong slug + # Type wrong slug - use element to find the form with phx-target view - |> render_change("update_slug_confirmation", %{"slug" => "wrong-slug"}) + |> element("#delete-custom-field-modal form") + |> render_change(%{"slug" => "wrong-slug"}) # Button should be disabled html = render(view) @@ -133,20 +136,21 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do {:ok, custom_field} = create_custom_field("test_field", :string) {:ok, custom_field_value} = create_custom_field_value(member, custom_field, "test") - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") # Open modal view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() - # Enter correct slug + # Enter correct slug - use element to find the form with phx-target view - |> render_change("update_slug_confirmation", %{"slug" => custom_field.slug}) + |> element("#delete-custom-field-modal form") + |> render_change(%{"slug" => custom_field.slug}) # Click confirm view - |> element("button", "Delete Custom Field and All Values") + |> element("#delete-custom-field-modal button", "Delete Custom Field and All Values") |> render_click() # Should show success message @@ -162,27 +166,28 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do assert {:ok, _} = Ash.get(Member, member.id) end - test "shows error when slug doesn't match", %{conn: conn} do + test "button remains disabled and custom field not deleted when slug doesn't match", %{ + conn: conn + } do {:ok, custom_field} = create_custom_field("test_field", :string) - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() - # Enter wrong slug + # Enter wrong slug - use element to find the form with phx-target view - |> render_change("update_slug_confirmation", %{"slug" => "wrong-slug"}) + |> element("#delete-custom-field-modal form") + |> render_change(%{"slug" => "wrong-slug"}) - # Try to confirm (button should be disabled, but test the handler anyway) - view - |> render_click("confirm_delete", %{}) + # Button should be disabled and we cannot click it + # The test verifies that the button is properly disabled in the UI + html = render(view) + assert html =~ ~r/disabled(?:=""|(?!\w))/ - # Should show error message - assert render(view) =~ "Slug does not match" - - # Custom field should still exist + # Custom field should still exist since deletion couldn't proceed assert {:ok, _} = Ash.get(CustomField, custom_field.id) end end @@ -191,10 +196,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do test "closes modal without deleting", %{conn: conn} do {:ok, custom_field} = create_custom_field("test_field", :string) - {:ok, view, _html} = live(conn, ~p"/custom_fields") + {:ok, view, _html} = live(conn, ~p"/settings") view - |> element("a", "Delete") + |> element("#custom-fields-component a", "Delete") |> render_click() # Modal should be visible @@ -202,7 +207,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do # Click cancel view - |> element("button", "Cancel") + |> element("#delete-custom-field-modal button", "Cancel") |> render_click() # Modal should be gone diff --git a/test/mv_web/live/profile_navigation_test.exs b/test/mv_web/live/profile_navigation_test.exs index 3222825..7cc6d3c 100644 --- a/test/mv_web/live/profile_navigation_test.exs +++ b/test/mv_web/live/profile_navigation_test.exs @@ -150,8 +150,6 @@ defmodule MvWeb.ProfileNavigationTest do "/members/new", "/custom_field_values", "/custom_field_values/new", - "/custom_fields", - "/custom_fields/new", "/users", "/users/new" ]