defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do @moduledoc """ Integration tests for field visibility dropdown functionality. Tests cover: - Field selection dropdown rendering - Toggling field visibility - URL parameter persistence - Select all / deselect all - Integration with member list display - Custom fields visibility """ use MvWeb.ConnCase, async: true import Phoenix.LiveViewTest require Ash.Query alias Mv.Membership.{CustomField, CustomFieldValue, Member} setup do # Create test members {:ok, member1} = Member |> Ash.Changeset.for_create(:create_member, %{ first_name: "Alice", last_name: "Anderson", email: "alice@example.com", street: "Main St", city: "Berlin" }) |> Ash.create() {:ok, member2} = Member |> Ash.Changeset.for_create(:create_member, %{ first_name: "Bob", last_name: "Brown", email: "bob@example.com", street: "Second St", city: "Hamburg" }) |> Ash.create() # Create custom field {:ok, custom_field} = CustomField |> Ash.Changeset.for_create(:create, %{ name: "membership_number", value_type: :string, show_in_overview: true }) |> Ash.create() # Create custom field values {:ok, _cfv1} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member1.id, custom_field_id: custom_field.id, value: "M001" }) |> Ash.create() {:ok, _cfv2} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member2.id, custom_field_id: custom_field.id, value: "M002" }) |> Ash.create() %{ member1: member1, member2: member2, custom_field: custom_field } end describe "field visibility dropdown" do test "renders dropdown button", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members") assert html =~ "Columns" assert html =~ ~s(aria-controls="field-visibility-menu") end test "opens dropdown when button is clicked", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Initially closed refute has_element?(view, "ul#field-visibility-menu") # Click button view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Should be open now assert has_element?(view, "ul#field-visibility-menu") end test "displays all member fields in dropdown", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() html = render(view) # Check for member fields (formatted labels) assert html =~ "First Name" or html =~ "first_name" assert html =~ "Email" or html =~ "email" assert html =~ "Street" or html =~ "street" end test "displays custom fields in dropdown", %{conn: conn, custom_field: custom_field} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() html = render(view) assert html =~ custom_field.name end end describe "field visibility toggling" do test "hiding a field removes it from display", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Verify email is visible initially html = render(view) assert html =~ "alice@example.com" # Open dropdown and hide email view |> element("button[aria-controls='field-visibility-menu']") |> render_click() view |> element("button[phx-click='select_item'][phx-value-item='email']") |> render_click() # Wait for update :timer.sleep(100) # Email should no longer be visible html = render(view) refute html =~ "alice@example.com" refute html =~ "bob@example.com" end test "hiding custom field removes it from display", %{conn: conn, custom_field: custom_field} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Verify custom field is visible initially html = render(view) assert html =~ "M001" or html =~ custom_field.name # Open dropdown and hide custom field view |> element("button[aria-controls='field-visibility-menu']") |> render_click() custom_field_id = custom_field.id custom_field_string = "custom_field_#{custom_field_id}" view |> element("button[phx-click='select_item'][phx-value-item='#{custom_field_string}']") |> render_click() # Wait for update :timer.sleep(100) # Custom field should no longer be visible html = render(view) refute html =~ "M001" refute html =~ "M002" end end describe "select all / deselect all" do test "select all makes all fields visible", %{conn: conn} do conn = conn_with_oidc_user(conn) # Start with some fields hidden {:ok, view, _html} = live(conn, "/members?fields=first_name") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Click select all view |> element("button[phx-click='select_all']") |> render_click() # Wait for update :timer.sleep(100) # All fields should be visible html = render(view) assert html =~ "alice@example.com" assert html =~ "Main St" assert html =~ "Berlin" end test "deselect all hides all fields except first_name", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Click deselect all view |> element("button[phx-click='select_none']") |> render_click() # Wait for update :timer.sleep(100) # Only first_name should be visible (it's always shown) html = render(view) # Email and street should be hidden refute html =~ "alice@example.com" refute html =~ "Main St" end end describe "URL parameter persistence" do test "field selection is persisted in URL", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown and hide email view |> element("button[aria-controls='field-visibility-menu']") |> render_click() view |> element("button[phx-click='select_item'][phx-value-item='email']") |> render_click() # Wait for URL update :timer.sleep(100) # Check that URL contains fields parameter # Note: In LiveView tests, we check the rendered HTML for the updated state # The actual URL update happens via push_patch end test "loading page with fields parameter applies selection", %{conn: conn} do conn = conn_with_oidc_user(conn) # Load with first_name and city explicitly set in URL # Note: Other fields may still be visible due to global settings {:ok, view, _html} = live(conn, "/members?fields=first_name,city") html = render(view) # first_name and city should be visible assert html =~ "Alice" assert html =~ "Berlin" # Note: email and street may still be visible if global settings allow it # This test verifies that the URL parameters work, not that they hide other fields end test "fields parameter works with custom fields", %{conn: conn, custom_field: custom_field} do conn = conn_with_oidc_user(conn) custom_field_id = custom_field.id # Load with custom field visible {:ok, view, _html} = live(conn, "/members?fields=first_name,custom_field_#{custom_field_id}") html = render(view) # Custom field should be visible assert html =~ "M001" or html =~ custom_field.name end end describe "integration with global settings" do test "respects global settings when no user selection", %{conn: conn} do # This test would require setting up global settings # For now, we verify that the system works with default settings conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members") # All fields should be visible by default assert html =~ "alice@example.com" assert html =~ "Main St" end test "user selection overrides global settings", %{conn: conn} do # This would require setting up global settings first # Then verifying that user selection takes precedence conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Hide a field via dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() view |> element("button[phx-click='select_item'][phx-value-item='email']") |> render_click() :timer.sleep(100) html = render(view) refute html =~ "alice@example.com" end end describe "edge cases" do test "handles empty fields parameter", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members?fields=") # Should fall back to global settings assert html =~ "alice@example.com" end test "handles invalid field names in URL", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members?fields=invalid_field,another_invalid") # Should ignore invalid fields and use defaults assert html =~ "alice@example.com" end test "handles custom field that doesn't exist", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members?fields=first_name,custom_field_nonexistent") # Should work without errors assert html =~ "Alice" end test "handles rapid toggling", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Rapidly toggle a field multiple times for _ <- 1..5 do view |> element("button[phx-click='select_item'][phx-value-item='email']") |> render_click() :timer.sleep(50) end # Should still work correctly html = render(view) assert html =~ "Alice" end end describe "accessibility" do test "dropdown has proper ARIA attributes", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members") assert html =~ ~s(aria-controls="field-visibility-menu") assert html =~ ~s(aria-haspopup="menu") assert html =~ ~s(role="button") end test "menu items have proper ARIA attributes when open", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() html = render(view) assert html =~ ~s(role="menu") assert html =~ ~s(role="menuitemcheckbox") assert html =~ ~s(aria-checked) end test "keyboard navigation works", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Check that elements are keyboard accessible html = render(view) assert html =~ ~s(tabindex="0") # Check that keyboard events are supported assert html =~ ~s(phx-keydown="select_item") assert html =~ ~s(phx-key="Enter") end test "keyboard activation with Enter key works", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Verify email is visible initially html = render(view) assert html =~ "alice@example.com" # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Simulate Enter key press on email field button view |> element("button[phx-click='select_item'][phx-value-item='email']") |> render_keydown(%{key: "Enter"}) # Wait for update :timer.sleep(100) # Email should no longer be visible html = render(view) refute html =~ "alice@example.com" end end end