Add groups to member detail view closes #374 #423
1 changed files with 323 additions and 0 deletions
323
test/mv_web/member_live/show_groups_display_test.exs
Normal file
323
test/mv_web/member_live/show_groups_display_test.exs
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
defmodule MvWeb.MemberLive.ShowGroupsDisplayTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for displaying groups in the member detail view (Issue #374).
|
||||||
|
|
||||||
|
Tests cover:
|
||||||
|
- Groups section visibility (with and without groups)
|
||||||
|
- Group badges with correct names and links to group detail pages
|
||||||
|
- Edge cases (one group, many groups)
|
||||||
|
- Security: groups section visible only when user may view member
|
||||||
|
- Accessibility: badges 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).
|
||||||
|
|
||||||
|
## Expected state
|
||||||
|
These tests fail until the Groups section is implemented on the member show page
|
||||||
|
(Issue #374: load groups in handle_params, add "Groups" section with badges and links).
|
||||||
|
"""
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# Assert Groups section in main content (section with h2 "Groups"), not sidebar link
|
||||||
|
assert has_element?(view, "main section h2", gettext("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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue