defmodule MvWeb.MemberLive.ShowGroupsDisplayTest do @moduledoc """ Tests for displaying groups in the member detail view (Issue #374). Tests cover: - Groups in Personal Data (with and without groups) - Group buttons with correct names and links to group detail pages - Edge cases (one group, many groups) - Security: groups visible only when user may view member - Accessibility: group links have role and aria-label ## Note on async async: false to avoid PostgreSQL deadlocks when creating members and groups in the same test run (same as IndexGroupsDisplayTest). """ use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest require Ash.Query use Gettext, backend: MvWeb.Gettext alias Mv.Membership.{Group, MemberGroup} describe "groups section" do setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, member} = Mv.Membership.create_member( %{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"}, actor: system_actor ) %{member: member, actor: system_actor} end test "displays Groups section when member has at least one group", %{ conn: conn, member: member, actor: actor } do {:ok, group} = Group |> Ash.Changeset.for_create(:create, %{name: "Board Members"}) |> Ash.create(actor: actor) {:ok, _mg} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group.id}) |> Ash.create(actor: actor) conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") assert html =~ gettext("Groups") assert html =~ group.name end test "displays all groups as badges with correct names when member is in multiple groups", %{ conn: conn, member: member, actor: actor } do {:ok, group1} = Group |> Ash.Changeset.for_create(:create, %{name: "Board Members"}) |> Ash.create(actor: actor) {:ok, group2} = Group |> Ash.Changeset.for_create(:create, %{name: "Active Members"}) |> Ash.create(actor: actor) {:ok, _mg1} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group1.id}) |> Ash.create(actor: actor) {:ok, _mg2} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group2.id}) |> Ash.create(actor: actor) conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") assert html =~ gettext("Groups") assert html =~ group1.name assert html =~ group2.name end test "displays Groups section when member has no groups (empty state)", %{ conn: conn, member: member } do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") # Groups are in Personal Data; label "Groups" and empty state "No groups" must be present assert html =~ gettext("Groups") assert html =~ gettext("No groups") end test "groups are loaded with member (single request returns all group names)", %{ conn: conn, member: member, actor: actor } do {:ok, group1} = Group |> Ash.Changeset.for_create(:create, %{name: "Alpha"}) |> Ash.create(actor: actor) {:ok, group2} = Group |> Ash.Changeset.for_create(:create, %{name: "Beta"}) |> Ash.create(actor: actor) {:ok, _mg1} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group1.id}) |> Ash.create(actor: actor) {:ok, _mg2} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group2.id}) |> Ash.create(actor: actor) conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") assert html =~ "Alpha" assert html =~ "Beta" end end describe "groups section links" do setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, member} = Mv.Membership.create_member( %{first_name: "Bob", last_name: "Brown", email: "bob@example.com"}, actor: system_actor ) {:ok, group} = Group |> Ash.Changeset.for_create(:create, %{name: "Board Members"}) |> Ash.create(actor: system_actor) {:ok, _mg} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group.id}) |> Ash.create(actor: system_actor) %{member: member, group: group} end test "each group badge links to group detail page with correct slug", %{ conn: conn, member: member, group: group } do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") # Link to group detail: /groups/:slug (slug is URL-friendly, e.g. "board-members") assert html =~ ~r/href="[^"]*\/groups\/#{Regex.escape(group.slug)}"|navigate="[^"]*\/groups\/#{Regex.escape(group.slug)}"/ end test "clicking group badge navigates to group detail page", %{ conn: conn, member: member, group: group } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, ~p"/members/#{member}") view |> element("a[href*='/groups/#{group.slug}']") |> render_click() assert_redirect(view, ~p"/groups/#{group.slug}") end end describe "groups section edge cases" do setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, member} = Mv.Membership.create_member( %{first_name: "Charlie", last_name: "Clark", email: "charlie@example.com"}, actor: system_actor ) %{member: member, actor: system_actor} end test "member in exactly one group shows single badge", %{ conn: conn, member: member, actor: actor } do {:ok, group} = Group |> Ash.Changeset.for_create(:create, %{name: "Solo Group"}) |> Ash.create(actor: actor) {:ok, _mg} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group.id}) |> Ash.create(actor: actor) conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") assert html =~ gettext("Groups") assert html =~ group.name end test "member in many groups shows all badges", %{ conn: conn, member: member, actor: actor } do group_names = Enum.map(1..5, fn i -> "Group #{i}" end) groups = Enum.map(group_names, fn name -> {:ok, g} = Group |> Ash.Changeset.for_create(:create, %{name: name}) |> Ash.create(actor: actor) g end) for g <- groups do {:ok, _mg} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: g.id}) |> Ash.create(actor: actor) end conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/members/#{member}") assert html =~ gettext("Groups") for name <- group_names do assert html =~ name end end end describe "groups section with read_only user" do @tag role: :read_only test "user with read permission sees Groups section", %{conn: conn} do system_actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, member} = Mv.Membership.create_member( %{first_name: "Diana", last_name: "Davis", email: "diana@example.com"}, actor: system_actor ) {:ok, group} = Group |> Ash.Changeset.for_create(:create, %{name: "Readers"}) |> Ash.create(actor: system_actor) {:ok, _mg} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group.id}) |> Ash.create(actor: system_actor) {:ok, _view, html} = live(conn, ~p"/members/#{member}") assert html =~ gettext("Groups") assert html =~ group.name end end describe "groups section accessibility" do setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, member} = Mv.Membership.create_member( %{first_name: "Eve", last_name: "Evans", email: "eve@example.com"}, actor: system_actor ) {:ok, group} = Group |> Ash.Changeset.for_create(:create, %{name: "A11y Group"}) |> Ash.create(actor: system_actor) {:ok, _mg} = MemberGroup |> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group.id}) |> Ash.create(actor: system_actor) %{member: member, group: group} end test "group badges have role and aria-label for screen readers", %{ conn: conn, member: member, group: group } do conn = conn_with_oidc_user(conn) {:ok, view, html} = live(conn, ~p"/members/#{member}") assert html =~ group.name # Badge has role="status" and aria-label indicating group membership (architecture: "Member of group X") assert has_element?(view, "[role='status'][aria-label*='#{group.name}']") end end end