defmodule MvWeb.CustomFieldLive.DeletionTest do @moduledoc """ 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 - Displaying correct member count - Slug confirmation input - Successful deletion with correct slug - Failed deletion with incorrect slug - Canceling deletion - Button states (enabled/disabled based on slug match) """ use MvWeb.ConnCase, async: true import Phoenix.LiveViewTest require Ash.Query alias Mv.Membership.{CustomField, CustomFieldValue, Member} setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() admin_role = Mv.Fixtures.role_fixture("admin") # Create admin user for testing (must have admin role to read/manage CustomField) {:ok, user} = Mv.Accounts.User |> Ash.Changeset.for_create(:register_with_password, %{ email: "admin#{System.unique_integer([:positive])}@mv.local", password: "testpassword123" }) |> Ash.create(actor: system_actor) {:ok, user} = user |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.update(actor: system_actor) user_with_role = Ash.load!(user, :role, domain: Mv.Accounts, actor: system_actor) conn = log_in_user(build_conn(), user_with_role) # Use English locale so "Delete" link text matches in assertions session = conn.private[:plug_session] || %{} conn = Plug.Test.init_test_session(conn, Map.put(session, "locale", "en")) %{conn: conn, user: user_with_role} end describe "delete button and modal" do test "opens modal with correct member count when delete is clicked", %{conn: conn} do {:ok, member} = create_member() {:ok, custom_field} = create_custom_field("test_field", :string) # Create custom field value create_custom_field_value(member, custom_field, "test") {:ok, view, _html} = live(conn, ~p"/settings") # Click delete button - find the delete link within the component view |> element("#custom-fields-component a", "Delete") |> render_click() # Modal should be visible assert has_element?(view, "#delete-custom-field-modal") # Should show correct member count (1 member) assert render(view) =~ "1 member has a value assigned for this custom field" # Should show the slug assert render(view) =~ custom_field.slug end test "shows correct plural form for multiple members", %{conn: conn} do {:ok, member1} = create_member() {:ok, member2} = create_member() {:ok, custom_field} = create_custom_field("test_field", :string) # Create values for both members create_custom_field_value(member1, custom_field, "test1") create_custom_field_value(member2, custom_field, "test2") {:ok, view, _html} = live(conn, ~p"/settings") view |> element("#custom-fields-component a", "Delete") |> render_click() # Should show plural form assert render(view) =~ "2 members have values assigned for this custom field" end 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"/settings") view |> element("#custom-fields-component a", "Delete") |> render_click() # Should show 0 members assert render(view) =~ "0 members have values assigned for this custom field" end end describe "slug confirmation input" 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"/settings") view |> element("#custom-fields-component a", "Delete") |> render_click() # Type in slug input - use element to find the form with phx-target view |> element("#delete-custom-field-modal form") |> render_change(%{"slug" => custom_field.slug}) # Confirm button should be enabled now (no disabled attribute on the confirm button) refute has_element?(view, "#delete-custom-field-modal button.btn-error[disabled]") end 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"/settings") view |> element("#custom-fields-component a", "Delete") |> render_click() # Type wrong slug - use element to find the form with phx-target view |> element("#delete-custom-field-modal form") |> render_change(%{"slug" => "wrong-slug"}) # Confirm button should be disabled assert has_element?(view, "#delete-custom-field-modal button.btn-error[disabled]") end end describe "confirm deletion" do test "successfully deletes custom field with correct slug", %{conn: conn} do {:ok, member} = create_member() {: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"/settings") # Open modal view |> element("#custom-fields-component a", "Delete") |> render_click() # Enter correct slug - use element to find the form with phx-target view |> element("#delete-custom-field-modal form") |> render_change(%{"slug" => custom_field.slug}) # Click confirm view |> element("#delete-custom-field-modal button", "Delete Custom Field and All Values") |> render_click() # Should show success message assert render(view) =~ "Data field deleted successfully" system_actor = Mv.Helpers.SystemActor.get_system_actor() # Custom field should be gone from database assert {:error, _} = Ash.get(CustomField, custom_field.id, actor: system_actor) # Custom field value should also be gone (CASCADE) assert {:error, _} = Ash.get(CustomFieldValue, custom_field_value.id, actor: system_actor) # Member should still exist assert {:ok, _} = Ash.get(Member, member.id, actor: system_actor) end 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"/settings") view |> element("#custom-fields-component a", "Delete") |> render_click() # Enter wrong slug - use element to find the form with phx-target view |> element("#delete-custom-field-modal form") |> render_change(%{"slug" => "wrong-slug"}) # Confirm button should be disabled and we cannot click it assert has_element?(view, "#delete-custom-field-modal button.btn-error[disabled]") # Custom field should still exist since deletion couldn't proceed system_actor = Mv.Helpers.SystemActor.get_system_actor() assert {:ok, _} = Ash.get(CustomField, custom_field.id, actor: system_actor) end end describe "cancel deletion" do test "closes modal without deleting", %{conn: conn} do {:ok, custom_field} = create_custom_field("test_field", :string) {:ok, view, _html} = live(conn, ~p"/settings") view |> element("#custom-fields-component a", "Delete") |> render_click() # Modal should be visible assert has_element?(view, "#delete-custom-field-modal") # Click cancel view |> element("#delete-custom-field-modal button", "Cancel") |> render_click() # Modal should be gone refute has_element?(view, "#delete-custom-field-modal") # Custom field should still exist system_actor = Mv.Helpers.SystemActor.get_system_actor() assert {:ok, _} = Ash.get(CustomField, custom_field.id, actor: system_actor) end end describe "create custom field" do test "submitting new data field form creates custom field and shows success", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/settings") # Open "New Data Field" form view |> element("#custom-fields-component button", "New Data Field") |> render_click() # Form is visible; submit with valid data form_params = %{ "custom_field" => %{ "name" => "Created via Form", "value_type" => "string", "description" => "", "required" => "false", "show_in_overview" => "true" } } view |> form("#custom-field-form-new-form", form_params) |> render_submit() # Success flash (FormComponent needs actor from parent; without it KeyError would occur) assert render(view) =~ "successfully" # Custom field was created in DB system_actor = Mv.Helpers.SystemActor.get_system_actor() search_name = "Created via Form" [custom_field] = Mv.Membership.CustomField |> Ash.Query.filter(name == ^search_name) |> Ash.read!(actor: system_actor) assert custom_field.value_type == :string end end # Helper functions (use code interface so actor is in validation context) defp create_member do system_actor = Mv.Helpers.SystemActor.get_system_actor() Mv.Membership.create_member( %{ first_name: "Test", last_name: "User#{System.unique_integer([:positive])}", email: "test#{System.unique_integer([:positive])}@example.com" }, actor: system_actor ) end defp create_custom_field(name, value_type) do system_actor = Mv.Helpers.SystemActor.get_system_actor() CustomField |> Ash.Changeset.for_create(:create, %{ name: "#{name}_#{System.unique_integer([:positive])}", value_type: value_type }) |> Ash.create(actor: system_actor) end defp create_custom_field_value(member, custom_field, value) do system_actor = Mv.Helpers.SystemActor.get_system_actor() CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member.id, custom_field_id: custom_field.id, value: %{"_union_type" => "string", "_union_value" => value} }) |> Ash.create(actor: system_actor) end defp log_in_user(conn, user) do conn |> Phoenix.ConnTest.init_test_session(%{}) |> AshAuthentication.Plug.Helpers.store_in_session(user) end end