defmodule MvWeb.MemberLive.IndexGroupsIntegrationTest do @moduledoc """ Tests for integration of groups with existing features in the member overview. Tests cover: - Groups column works with Field Visibility (column can be hidden) - Groups filter works with Custom Field filters - Groups sorting works with other sortings - Groups work with Membership Fee Status filter - Groups work with existing search (but not testing search integration itself) - Member index search by group name returns members in that group (Issue #375) """ # async: false to prevent PostgreSQL deadlocks when creating members and groups use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest require Ash.Query alias Mv.Helpers.SystemActor alias Mv.Membership.{CustomField, CustomFieldValue, Group, MemberGroup} alias Mv.MembershipFees.{MembershipFeeCycle, MembershipFeeType} setup do system_actor = SystemActor.get_system_actor() # Create test members {:ok, member1} = Mv.Membership.create_member( %{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"}, actor: system_actor ) {:ok, member2} = Mv.Membership.create_member( %{first_name: "Bob", last_name: "Brown", email: "bob@example.com"}, actor: system_actor ) # Create test groups {:ok, group1} = Group |> Ash.Changeset.for_create(:create, %{name: "Board Members"}) |> Ash.create(actor: system_actor) # Create member-group associations {:ok, _mg1} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member1.id, group_id: group1.id}) |> Ash.create(actor: system_actor) # Create custom field for filter integration test {:ok, custom_field} = CustomField |> Ash.Changeset.for_create(:create, %{ name: "newsletter", value_type: :boolean, show_in_overview: false }) |> Ash.create(actor: system_actor) # Create custom field value for member1 {:ok, _cfv} = CustomFieldValue |> Ash.Changeset.for_create(:create, %{ member_id: member1.id, custom_field_id: custom_field.id, value: %{"_union_type" => "boolean", "_union_value" => true} }) |> Ash.create(actor: system_actor) %{ member1: member1, member2: member2, group1: group1, custom_field: custom_field } end test "groups column works with field visibility", %{ conn: conn, member1: member1, group1: group1 } do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members") # Verify groups column is visible by default (header and content) assert html =~ group1.name assert html =~ member1.first_name assert html =~ "Groups" end test "groups filter works with custom field filters", %{ conn: conn, member1: member1, group1: group1 } do # Verify group filter applies; boolean filters live in the filter dropdown and # are exercised in member filter tests. Here we only assert group filter works. conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") view |> element("button[aria-label='Filter members']") |> render_click() view |> element("[data-testid='member-filter-form']") |> render_change(%{"group_#{group1.id}" => "in", "payment_filter" => "all"}) html = render(view) assert html =~ member1.first_name end test "groups sorting works with other sortings", %{ conn: conn, member1: member1, member2: member2 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=asc") # Apply groups sorting (should combine with existing sort) view |> element("[data-testid='groups']") |> render_click() # Verify both sorts are applied (or groups sort replaces first_name sort) html = render(view) assert html =~ member1.first_name assert html =~ member2.first_name # Sort by groups was applied (URL may include query= and other default params) assert has_element?(view, "[data-testid='groups'][aria-label*='ascending']") end test "groups work with membership fee status filter", %{ conn: conn, member1: member1, group1: group1 } do system_actor = SystemActor.get_system_actor() # Create a membership fee type and cycle for member1 {:ok, fee_type} = MembershipFeeType |> Ash.Changeset.for_create(:create, %{ name: "Test Fee", amount: Decimal.new("50.00"), interval: :yearly }) |> Ash.create(actor: system_actor) # Set member's fee type so get_last_completed_cycle finds the cycle (uses member.membership_fee_type) {:ok, _member1} = Mv.Membership.update_member(member1, %{membership_fee_type_id: fee_type.id}, actor: system_actor ) {:ok, _cycle} = MembershipFeeCycle |> Ash.Changeset.for_create(:create, %{ member_id: member1.id, membership_fee_type_id: fee_type.id, cycle_start: ~D[2024-01-01], amount: Decimal.new("50.00"), status: :paid }) |> Ash.create(actor: system_actor) conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members?group_#{group1.id}=in&cycle_status_filter=paid") assert html =~ "Members" # member1 has a group and a paid cycle; page should load with both filters assert html =~ member1.first_name end test "groups work with existing search (not testing search integration)", %{ conn: conn, member1: member1, member2: member2, group1: group1 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Apply group filter view |> element("button[aria-label='Filter members']") |> render_click() view |> element("[data-testid='member-filter-form']") |> render_change(%{"group_#{group1.id}" => "in", "payment_filter" => "all"}) # Apply search (this tests that filter and search work together; # search form is in SearchBarComponent with phx-submit="search") view |> element("form[phx-submit='search']") |> render_submit(%{"query" => "Alice"}) # Verify filter and search both work html = render(view) assert html =~ member1.first_name refute html =~ member2.first_name # Note: We're not testing that group names are searchable # (that's part of Issue #5 - Search Integration) end test "member index search by group name returns members in that group", %{ conn: conn, member1: member1, member2: member2, group1: group1 } do # member1 is in group1 "Board Members", member2 is not conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") view |> element("form[phx-submit='search']") |> render_submit(%{"query" => group1.name}) html = render(view) assert html =~ member1.first_name refute html =~ member2.first_name end test "all filters and sortings work together", %{ conn: conn, member1: member1, group1: group1 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Apply group filter view |> element("button[aria-label='Filter members']") |> render_click() view |> element("[data-testid='member-filter-form']") |> render_change(%{"group_#{group1.id}" => "in", "payment_filter" => "all"}) # Apply sorting view |> element("[data-testid='groups']") |> render_click() # Apply search view |> element("form[phx-submit='search']") |> render_submit(%{"query" => "Alice"}) # Verify group filter, sort, and search are all applied html = render(view) assert html =~ member1.first_name assert has_element?(view, "[data-testid='groups'][aria-label*='ascending']") end end