363 lines
11 KiB
Elixir
363 lines
11 KiB
Elixir
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
|