defmodule MvWeb.Components.FieldVisibilityDropdownComponentTest do @moduledoc """ Tests for FieldVisibilityDropdownComponent LiveComponent. """ use MvWeb.ConnCase, async: true import Phoenix.LiveViewTest alias MvWeb.Components.FieldVisibilityDropdownComponent # Helper to create test assigns defp create_assigns(overrides \\ %{}) do default_assigns = %{ id: "test-dropdown", all_fields: [:first_name, :email, :street, "custom_field_123"], custom_fields: [ %{id: "123", name: "Custom Field 1"} ], selected_fields: %{ "first_name" => true, "email" => true, "street" => false, "custom_field_123" => true } } Map.merge(default_assigns, overrides) end describe "update/2" do test "initializes with default values" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) assert socket.assigns.id == "test-dropdown" assert socket.assigns.open == false assert socket.assigns.all_fields == assigns.all_fields assert socket.assigns.selected_fields == assigns.selected_fields end test "preserves existing open state" do assigns = create_assigns() existing_socket = %{assigns: %{open: true}} {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, existing_socket) assert socket.assigns.open == true end test "handles missing optional assigns" do minimal_assigns = %{id: "test"} {:ok, socket} = FieldVisibilityDropdownComponent.update(minimal_assigns, %{}) assert socket.assigns.all_fields == [] assert socket.assigns.custom_fields == [] assert socket.assigns.selected_fields == %{} end end describe "render/1" do test "renders dropdown button" do assigns = create_assigns() html = render_component(FieldVisibilityDropdownComponent, assigns) assert html =~ "Columns" assert html =~ "hero-adjustments-horizontal" assert has_element?(html, "button[aria-controls='field-visibility-menu']") end test "renders dropdown menu when open" do assigns = create_assigns() |> Map.put(:open, true) html = render_component(FieldVisibilityDropdownComponent, assigns) assert has_element?(html, "ul#field-visibility-menu") assert html =~ "All" assert html =~ "None" end test "does not render menu when closed" do assigns = create_assigns() |> Map.put(:open, false) html = render_component(FieldVisibilityDropdownComponent, assigns) refute has_element?(html, "ul#field-visibility-menu") end test "renders member fields" do assigns = create_assigns() |> Map.put(:open, true) html = render_component(FieldVisibilityDropdownComponent, assigns) # Field names should be formatted (first_name -> First Name) assert html =~ "First Name" or html =~ "first_name" assert html =~ "Email" or html =~ "email" assert html =~ "Street" or html =~ "street" end test "renders custom fields when custom fields exist" do assigns = create_assigns() |> Map.put(:open, true) html = render_component(FieldVisibilityDropdownComponent, assigns) # Custom field name assert html =~ "Custom Field 1" end test "renders checkboxes with correct checked state" do assigns = create_assigns() |> Map.put(:open, true) html = render_component(FieldVisibilityDropdownComponent, assigns) # first_name should be checked (aria-checked="true") assert html =~ ~s(aria-checked="true") assert html =~ ~s(phx-value-item="first_name") # street should not be checked (aria-checked="false") assert html =~ ~s(phx-value-item="street") # Note: The visual checkbox state is handled by CSS classes and aria-checked attribute end test "includes accessibility attributes" do assigns = create_assigns() |> Map.put(:open, true) html = render_component(FieldVisibilityDropdownComponent, assigns) assert html =~ ~s(aria-controls="field-visibility-menu") assert html =~ ~s(aria-haspopup="menu") assert html =~ ~s(role="button") assert html =~ ~s(role="menu") assert html =~ ~s(role="menuitemcheckbox") end test "formats member field labels correctly" do assigns = create_assigns() |> Map.put(:open, true) html = render_component(FieldVisibilityDropdownComponent, assigns) # Field names should be formatted (first_name -> First Name) assert html =~ "First Name" or html =~ "first_name" end test "uses custom field names from custom_fields prop" do assigns = create_assigns() |> Map.put(:open, true) |> Map.put(:custom_fields, [ %{id: "123", name: "Membership Number"} ]) html = render_component(FieldVisibilityDropdownComponent, assigns) assert html =~ "Membership Number" end test "falls back to ID when custom field not found" do assigns = create_assigns() |> Map.put(:open, true) # Empty custom fields list |> Map.put(:custom_fields, []) html = render_component(FieldVisibilityDropdownComponent, assigns) # Should show something like "Custom Field 123" assert html =~ "custom_field_123" or html =~ "Custom Field" end end describe "handle_event/2" do test "toggle_dropdown toggles open state" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) assert socket.assigns.open == false {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event("toggle_dropdown", %{}, socket) assert socket.assigns.open == true {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event("toggle_dropdown", %{}, socket) assert socket.assigns.open == false end test "close_dropdown sets open to false" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) socket = assign(socket, :open, true) {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event("close_dropdown", %{}, socket) assert socket.assigns.open == false end test "select_item toggles field visibility" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) assert socket.assigns.selected_fields["first_name"] == true {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event( "select_item", %{"item" => "first_name"}, socket ) assert socket.assigns.selected_fields["first_name"] == false {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event( "select_item", %{"item" => "first_name"}, socket ) assert socket.assigns.selected_fields["first_name"] == true end test "select_item defaults to true for missing fields" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event( "select_item", %{"item" => "new_field"}, socket ) # Toggled from default true assert socket.assigns.selected_fields["new_field"] == false end test "select_item sends message to parent" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) FieldVisibilityDropdownComponent.handle_event( "select_item", %{"item" => "first_name"}, socket ) # Check that message was sent (would be verified in integration test) # For unit test, we just verify the state change assert_receive {:field_toggled, "first_name", false} end test "select_all sets all fields to true" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event("select_all", %{}, socket) assert socket.assigns.selected_fields["first_name"] == true assert socket.assigns.selected_fields["email"] == true assert socket.assigns.selected_fields["street"] == true assert socket.assigns.selected_fields["custom_field_123"] == true end test "select_all sends message to parent" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) FieldVisibilityDropdownComponent.handle_event("select_all", %{}, socket) assert_receive {:fields_selected, selection} assert selection["first_name"] == true assert selection["email"] == true end test "select_none sets all fields to false" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event("select_none", %{}, socket) assert socket.assigns.selected_fields["first_name"] == false assert socket.assigns.selected_fields["email"] == false assert socket.assigns.selected_fields["street"] == false assert socket.assigns.selected_fields["custom_field_123"] == false end test "select_none sends message to parent" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) FieldVisibilityDropdownComponent.handle_event("select_none", %{}, socket) assert_receive {:fields_selected, selection} assert selection["first_name"] == false assert selection["email"] == false end test "handles custom field toggle" do assigns = create_assigns() {:ok, socket} = FieldVisibilityDropdownComponent.update(assigns, %{}) {:noreply, socket} = FieldVisibilityDropdownComponent.handle_event( "select_item", %{"item" => "custom_field_123"}, socket ) assert socket.assigns.selected_fields["custom_field_123"] == false end end describe "integration with LiveView" do test "component can be rendered in LiveView" do conn = conn_with_oidc_user(build_conn()) {:ok, view, _html} = live(conn, "/members") # Check that component is rendered assert has_element?(view, "button[aria-controls='field-visibility-menu']") end test "clicking button opens dropdown" do conn = conn_with_oidc_user(build_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 "toggling field updates selection" do conn = conn_with_oidc_user(build_conn()) {:ok, view, _html} = live(conn, "/members") # Open dropdown view |> element("button[aria-controls='field-visibility-menu']") |> render_click() # Toggle a field view |> element("button[phx-click='select_item'][phx-value-item='first_name']") |> render_click() # Component should update (verified by state change) # In a real scenario, this would trigger a reload of members end end end