Merge pull request 'finalize groups' (#437) from feature/finalize-groups into main
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #437
This commit is contained in:
simon 2026-02-23 17:27:48 +01:00
commit be9d12f181
18 changed files with 388 additions and 474 deletions

View file

@ -19,6 +19,7 @@ defmodule MvWeb.GroupLive.FormTest do
test "form renders with empty fields", %{conn: conn} do
{:ok, view, html} = live(conn, "/groups/new")
# OR-chain for i18n (Create Group / Gruppe erstellen)
assert html =~ gettext("Create Group") or html =~ "create" or html =~ "Gruppe erstellen"
assert has_element?(view, "form")
end
@ -65,6 +66,7 @@ defmodule MvWeb.GroupLive.FormTest do
|> form("#group-form", group: form_data)
|> render_submit()
# OR-chain for i18n (required/erforderlich) and validation message wording
assert html =~ gettext("required") or html =~ "name" or html =~ "error" or
html =~ "erforderlich"
end
@ -80,6 +82,7 @@ defmodule MvWeb.GroupLive.FormTest do
|> form("#group-form", group: form_data)
|> render_submit()
# OR-chain for i18n (length/Länge) and validation message
assert html =~ "100" or html =~ "length" or html =~ "error" or html =~ "Länge"
end
@ -98,6 +101,7 @@ defmodule MvWeb.GroupLive.FormTest do
|> form("#group-form", group: form_data)
|> render_submit()
# OR-chain for i18n (length/Länge) and validation message
assert html =~ "500" or html =~ "length" or html =~ "error" or html =~ "Länge"
end
@ -116,6 +120,7 @@ defmodule MvWeb.GroupLive.FormTest do
|> render_submit()
# Check for a validation error on the name field in a robust way
# OR-chain for i18n and validation message (already taken)
assert html =~ "name" or html =~ gettext("has already been taken")
end
@ -131,6 +136,7 @@ defmodule MvWeb.GroupLive.FormTest do
|> form("#group-form", group: form_data)
|> render_submit()
# OR-chain for i18n (error/Fehler, invalid/ungültig)
assert html =~ "error" or html =~ "invalid" or html =~ "Fehler" or html =~ "ungültig"
end
end
@ -196,6 +202,7 @@ defmodule MvWeb.GroupLive.FormTest do
|> form("#group-form", group: form_data)
|> render_submit()
# OR-chain for i18n (already taken / bereits vergeben) and validation wording
assert html =~ "already" or html =~ "taken" or html =~ "exists" or html =~ "error" or
html =~ "bereits" or html =~ "vergeben"
end
@ -205,7 +212,7 @@ defmodule MvWeb.GroupLive.FormTest do
{:ok, _view, html} = live(conn, "/groups/#{group.slug}/edit")
# Slug should not be in form (it's immutable)
# Slug should not be in form (it's immutable); regex for input element
refute html =~ ~r/slug.*input/i or html =~ ~r/input.*slug/i
end
end

View file

@ -40,13 +40,14 @@ defmodule MvWeb.GroupLive.IndexTest do
assert html =~ "Test Group"
assert html =~ "Test description"
# Member count should be displayed (0 for empty group)
# OR-chain for i18n (Members/Mitglieder) and alternate copy for count
assert html =~ "0" or html =~ gettext("Members") or html =~ "Mitglieder"
end
test "displays 'Create Group' button for admin users", %{conn: conn} do
{:ok, _view, html} = live(conn, "/groups")
# OR-chain for i18n (Create Group / Gruppe erstellen) and alternate wording
assert html =~ gettext("Create Group") or html =~ "create" or html =~ "new" or
html =~ "Gruppe erstellen"
end
@ -54,7 +55,7 @@ defmodule MvWeb.GroupLive.IndexTest do
test "displays empty state when no groups exist", %{conn: conn} do
{:ok, _view, html} = live(conn, "/groups")
# Should show empty state or empty list message
# OR-chain for i18n (No groups / Keine Gruppen) and alternate empty state copy
assert html =~ gettext("No groups") or html =~ "0" or html =~ "empty" or
html =~ "Keine Gruppen"
end
@ -76,6 +77,7 @@ defmodule MvWeb.GroupLive.IndexTest do
{:ok, _view, html} = live(conn, "/groups")
# Long description may be truncated in UI
assert html =~ long_description or html =~ String.slice(long_description, 0, 100)
end
end
@ -109,7 +111,7 @@ defmodule MvWeb.GroupLive.IndexTest do
# Should be able to see groups
assert html =~ gettext("Groups")
# Should NOT see create button
# Read-only must not see create button (OR for i18n)
refute html =~ gettext("Create Group") or html =~ "create"
end
end
@ -177,7 +179,7 @@ defmodule MvWeb.GroupLive.IndexTest do
final_count = Agent.get(query_count_agent, & &1)
:telemetry.detach(handler_id)
# Member count should be displayed (should be 2)
# OR-chain for i18n (Members/Mitglieder) and count display
assert html =~ "2" or html =~ gettext("Members") or html =~ "Mitglieder"
# Verify query count is reasonable (member count should be calculated efficiently)

View file

@ -62,7 +62,7 @@ defmodule MvWeb.GroupLive.IntegrationTest do
assert html =~ "Updated Workflow Test Group"
assert html =~ "Updated description"
# Slug should remain unchanged
# OR-chain: slug may appear as UUID or normalized slug in copy
assert html =~ original_slug or html =~ "workflow-test-group"
end
@ -101,7 +101,7 @@ defmodule MvWeb.GroupLive.IntegrationTest do
# View group via slug
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
# Member count should be 2
# OR-chain for i18n (Members/Mitglieder); member names may be first or last
assert html =~ "2" or html =~ gettext("Members") or html =~ "Mitglieder"
assert html =~ member1.first_name or html =~ member1.last_name
assert html =~ member2.first_name or html =~ member2.last_name

View file

@ -22,12 +22,13 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
|> element("button", "Add Member")
|> render_click()
html = render(view)
# Search input should have proper ARIA attributes
assert html =~ ~r/aria-label/ ||
html =~ ~r/aria-autocomplete/ ||
html =~ ~r/role=["']combobox["']/
# OR-chain: at least one of these ARIA/role attributes must be present
assert has_element?(view, "[data-testid=group-show-member-search-input][aria-label]") or
has_element?(
view,
"[data-testid=group-show-member-search-input][aria-autocomplete]"
) or
has_element?(view, "[data-testid=group-show-member-search-input][role=combobox]")
end
test "search input has correct aria-label and aria-autocomplete attributes", %{conn: conn} do
@ -35,16 +36,14 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
html = render(view)
# Search input should have ARIA attributes
assert html =~ ~r/aria-label.*[Ss]earch.*member/ ||
html =~ ~r/aria-autocomplete=["']list["']/
assert has_element?(
view,
"[data-testid=group-show-member-search-input][aria-autocomplete=list]"
)
end
test "remove button has aria-label with tooltip text", %{conn: conn} do
@ -67,11 +66,7 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
html = render(view)
# Remove button should have aria-label
assert html =~ ~r/aria-label.*[Rr]emove/ ||
html =~ ~r/aria-label.*member/i
assert has_element?(view, "[data-testid=group-show-remove-member][aria-label]")
end
test "add button has correct aria-label", %{conn: conn} do
@ -79,16 +74,11 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
html = render(view)
# Add button should have aria-label
assert html =~ ~r/aria-label.*[Aa]dd/ ||
html =~ ~r/button.*[Aa]dd/
assert has_element?(view, "[data-testid=group-show-add-selected-members-btn][aria-label]")
end
end
@ -100,16 +90,11 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
html = render(view)
# Inline add member area should have focusable elements
assert html =~ ~r/input|button/ ||
html =~ "#member-search-input"
assert has_element?(view, "[data-testid=group-show-member-search-input]")
end
test "inline input can be closed", %{conn: conn} do
@ -117,17 +102,11 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
assert has_element?(view, "#member-search-input")
# Click Add Member button again to close (or add a member to close it)
# For now, we verify the input is visible when opened
html = render(view)
assert html =~ "#member-search-input" || has_element?(view, "#member-search-input")
assert has_element?(view, "[data-testid=group-show-member-search-input]")
end
test "enter/space activates buttons when focused", %{conn: conn} do
@ -148,17 +127,14 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Select member
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Bob"})
@ -167,14 +143,11 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
|> element("[data-member-id='#{member.id}']")
|> render_click()
# Add button should be enabled and clickable
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Should succeed (member should appear in list)
html = render(view)
assert html =~ "Bob"
assert has_element?(view, "[data-testid=group-show-members-table]", "Bob")
end
test "focus management: focus is set to input when opened", %{conn: conn} do
@ -184,16 +157,11 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
html = render(view)
# Input should be visible and focusable
assert html =~ "#member-search-input" ||
html =~ ~r/autofocus|tabindex/
assert has_element?(view, "[data-testid=group-show-member-search-input]")
end
end
@ -203,16 +171,11 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
html = render(view)
# Input should have aria-label
assert html =~ ~r/aria-label.*[Ss]earch.*member/ ||
html =~ ~r/aria-label/
assert has_element?(view, "[data-testid=group-show-member-search-input][aria-label]")
end
test "search results are properly announced", %{conn: conn} do
@ -231,27 +194,20 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Search
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Charlie"})
html = render(view)
# Search results should have proper ARIA attributes
assert html =~ ~r/role=["']listbox["']/ ||
html =~ ~r/role=["']option["']/ ||
html =~ "Charlie"
assert has_element?(view, "#member-dropdown[role=listbox]")
assert has_element?(view, "#member-dropdown", "Charlie")
end
test "flash messages are properly announced", %{conn: conn} do
@ -270,16 +226,14 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Add member
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "David"})
@ -289,13 +243,10 @@ defmodule MvWeb.GroupLive.ShowAccessibilityTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
html = render(view)
# Member should appear in list (no flash message)
assert html =~ "David"
assert has_element?(view, "[data-testid=group-show-members-table]", "David")
end
end
end

View file

@ -34,9 +34,8 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
select_member(view, member)
add_selected(view)
html = render(view)
assert html =~ "Alice"
assert html =~ "Johnson"
assert has_element?(view, "[data-testid=group-show-members-table]", "Alice")
assert has_element?(view, "[data-testid=group-show-members-table]", "Johnson")
end
test "member is successfully added to group (verified in list)", %{conn: conn} do
@ -55,16 +54,14 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input and add member
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Bob"})
@ -74,14 +71,11 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
html = render(view)
# Verify member appears in group list (no success flash message)
assert html =~ "Bob"
assert html =~ "Smith"
assert has_element?(view, "[data-testid=group-show-members-table]", "Bob")
assert has_element?(view, "[data-testid=group-show-members-table]", "Smith")
end
test "group member list updates automatically after add", %{conn: conn} do
@ -98,21 +92,18 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
actor: system_actor
)
{:ok, view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Initially member should NOT be in list
refute html =~ "Charlie"
refute has_element?(view, "[data-testid=group-show-members-table]", "Charlie")
# Add member
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Charlie"})
@ -122,13 +113,11 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Member should now appear in list
html = render(view)
assert html =~ "Charlie"
assert html =~ "Brown"
assert has_element?(view, "[data-testid=group-show-members-table]", "Charlie")
assert has_element?(view, "[data-testid=group-show-members-table]", "Brown")
end
test "member count updates automatically after add", %{conn: conn} do
@ -152,11 +141,11 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Add member
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
@ -169,7 +158,7 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Count should have increased
@ -196,14 +185,14 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
assert has_element?(view, "#member-search-input")
assert has_element?(view, "[data-testid=group-show-member-search-input]")
# Add member
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
@ -216,11 +205,10 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Inline input should be closed (Add Member button should be visible again)
refute has_element?(view, "#member-search-input")
refute has_element?(view, "[data-testid=group-show-member-search-input]")
end
test "Cancel button closes inline add member area without adding", %{conn: conn} do
@ -229,7 +217,7 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
open_add_member(view)
assert has_element?(view, "#member-search-input")
assert has_element?(view, "[data-testid=group-show-member-search-input]")
assert has_element?(view, "button[phx-click='hide_add_member_input']")
cancel_add_member(view)
@ -263,7 +251,7 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Try to add same member again
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Member should not appear in search (filtered out)
@ -281,12 +269,12 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button", "Add")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Should show error
# OR-chain for i18n and alternate error wording (already in group / duplicate)
html = render(view)
assert html =~ gettext("already in group") || html =~ ~r/already.*group|duplicate/i
assert html =~ gettext("already in group") or html =~ ~r/already.*group|duplicate/i
end
end
@ -300,7 +288,7 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Try to add with invalid member ID (if possible)
@ -331,11 +319,10 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Inline input should be open
assert has_element?(view, "#member-search-input")
assert has_element?(view, "[data-testid=group-show-member-search-input]")
# If error occurs, inline input should remain open
# (Implementation will handle this)
@ -348,11 +335,10 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Add button should be disabled
assert has_element?(view, "button[phx-click='add_selected_members'][disabled]")
assert has_element?(view, "[data-testid=group-show-add-selected-members-btn][disabled]")
end
end
@ -375,11 +361,11 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Add member to empty group
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
@ -392,12 +378,10 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Member should be added
html = render(view)
assert html =~ "Henry"
assert has_element?(view, "[data-testid=group-show-members-table]", "Henry")
end
test "add works when member is already in other groups", %{conn: conn} do
@ -424,11 +408,11 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
# Add same member to group2 (should work)
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
# phx-change is on the form, so we need to trigger it via the form
@ -441,12 +425,10 @@ defmodule MvWeb.GroupLive.ShowAddMemberTest do
|> render_click()
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
# Member should be added to group2
html = render(view)
assert html =~ "Isabel"
assert has_element?(view, "[data-testid=group-show-members-table]", "Isabel")
end
end

View file

@ -22,18 +22,18 @@ defmodule MvWeb.GroupLive.ShowAddRemoveMembersTest do
test "Add Member button is visible for users with :update permission", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ gettext("Add Member") or html =~ "Add Member"
assert has_element?(view, "button[phx-click='show_add_member_input']")
end
@tag role: :read_only
test "Add Member button is NOT visible for users without :update permission", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
refute html =~ gettext("Add Member")
refute has_element?(view, "button[phx-click='show_add_member_input']")
end
test "Add Member button is positioned above member table", %{conn: conn} do
@ -61,11 +61,7 @@ defmodule MvWeb.GroupLive.ShowAddRemoveMembersTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Remove button should exist (can be icon button with trash icon)
html = render(view)
assert html =~ "Remove" or html =~ "remove" or html =~ "trash" or
html =~ ~r/hero-trash|hero-x-mark/
assert has_element?(view, "[data-testid=group-show-remove-member]")
end
@tag role: :read_only
@ -78,10 +74,9 @@ defmodule MvWeb.GroupLive.ShowAddRemoveMembersTest do
actor: system_actor
)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Remove button should NOT exist (check for trash icon or remove button specifically)
refute html =~ "hero-trash" or html =~ ~r/<button[^>]*remove_member/
refute has_element?(view, "[data-testid=group-show-remove-member]")
end
end
@ -110,10 +105,7 @@ defmodule MvWeb.GroupLive.ShowAddRemoveMembersTest do
|> element("button", gettext("Add Member"))
|> render_click()
html = render(view)
assert html =~ gettext("Search for a member...") ||
html =~ ~r/search.*member/i
assert has_element?(view, "[data-testid=group-show-member-search-input]")
end
test "Add button (plus icon) is disabled until member selected", %{conn: conn} do
@ -121,15 +113,11 @@ defmodule MvWeb.GroupLive.ShowAddRemoveMembersTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", gettext("Add Member"))
|> element("button[phx-click='show_add_member_input']")
|> render_click()
html = render(view)
# Add button should exist and be disabled initially
assert has_element?(view, "button[phx-click='add_selected_members'][disabled]") ||
html =~ ~r/disabled/
assert has_element?(view, "[data-testid=group-show-add-selected-members-btn][disabled]")
end
end
end

View file

@ -52,8 +52,7 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
|> render_click()
# Should succeed (admin has :update permission, member should appear in list)
html = render(view)
assert html =~ "Alice"
assert has_element?(view, "[data-testid=group-show-members-table]", "Alice")
end
@tag role: :read_only
@ -78,9 +77,7 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
# Note: If button is hidden, we can't click it, but we test the event handler
# by trying to send the event directly if possible
# For now, we verify that the button is not visible
html = render(view)
refute html =~ "Add Member"
refute has_element?(view, "button[phx-click='show_add_member_input']")
end
test "remove member event handler checks :update permission", %{conn: conn} do
@ -103,14 +100,11 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Remove member (should succeed for admin)
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Should succeed (member should no longer be in list)
html = render(view)
refute html =~ "Charlie"
refute has_element?(view, "[data-testid=group-show-members-table]", "Charlie")
end
@tag role: :read_only
@ -134,11 +128,7 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Remove button should not be visible
html = render(view)
# Read-only user should NOT see Remove button (check for trash icon or remove button specifically)
refute html =~ "hero-trash" or html =~ ~r/<button[^>]*remove_member/
refute has_element?(view, "[data-testid=group-show-remove-member]")
end
test "error flash message on unauthorized access", %{conn: conn} do
@ -174,10 +164,10 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
actor: system_actor
)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Admin should see buttons
assert html =~ "Add Member" || html =~ "Remove"
assert has_element?(view, "button[phx-click='show_add_member_input']")
assert has_element?(view, "[data-testid=group-show-remove-member]")
end
@tag role: :read_only
@ -185,10 +175,9 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
_system_actor = Mv.Helpers.SystemActor.get_system_actor()
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Read-only user should NOT see Add Member button
refute html =~ "Add Member"
refute has_element?(view, "button[phx-click='show_add_member_input']")
end
@tag role: :read_only
@ -210,21 +199,18 @@ defmodule MvWeb.GroupLive.ShowAuthorizationTest do
actor: system_actor
)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Read-only user should NOT see Remove button (check for trash icon or remove button specifically)
refute html =~ "hero-trash" or html =~ ~r/<button[^>]*remove_member/
refute has_element?(view, "[data-testid=group-show-remove-member]")
end
@tag role: :read_only
test "inline add member area cannot be opened for unauthorized users", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Inline input should not be accessible (button hidden)
refute html =~ "Add Member"
refute html =~ "#member-search-input"
refute has_element?(view, "button[phx-click='show_add_member_input']")
end
end

View file

@ -305,9 +305,8 @@ defmodule MvWeb.GroupLive.ShowIntegrationTest do
|> render_click()
# Both members should be in list
html = render(view)
assert html =~ "Frank"
assert html =~ "Grace"
assert has_element?(view, "[data-testid=group-show-members-table]", "Frank")
assert has_element?(view, "[data-testid=group-show-members-table]", "Grace")
end
test "multiple members can be removed sequentially", %{conn: conn} do
@ -343,11 +342,11 @@ defmodule MvWeb.GroupLive.ShowIntegrationTest do
actor: system_actor
)
{:ok, view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Both should be in list initially
assert html =~ "Henry"
assert html =~ "Isabel"
assert has_element?(view, "[data-testid=group-show-members-table]", "Henry")
assert has_element?(view, "[data-testid=group-show-members-table]", "Isabel")
# Remove first member
view
@ -360,9 +359,8 @@ defmodule MvWeb.GroupLive.ShowIntegrationTest do
|> render_click()
# Both should be removed
html = render(view)
refute html =~ "Henry"
refute html =~ "Isabel"
refute has_element?(view, "[data-testid=group-show-members-table]", "Henry")
refute has_element?(view, "[data-testid=group-show-members-table]", "Isabel")
end
test "add and remove can be mixed", %{conn: conn} do
@ -424,9 +422,8 @@ defmodule MvWeb.GroupLive.ShowIntegrationTest do
|> render_click()
# Only member2 should remain
html = render(view)
refute html =~ "Jack"
assert html =~ "Kate"
refute has_element?(view, "[data-testid=group-show-members-table]", "Jack")
assert has_element?(view, "[data-testid=group-show-members-table]", "Kate")
end
end
end

View file

@ -34,21 +34,16 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Type exact name
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Jonathan"})
html = render(view)
assert html =~ "Jonathan"
assert html =~ "Smith"
assert has_element?(view, "#member-dropdown", "Jonathan")
assert has_element?(view, "#member-dropdown", "Smith")
end
test "search finds member by partial name (fuzzy)", %{conn: conn} do
@ -68,22 +63,16 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Type partial name
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Jon"})
html = render(view)
# Fuzzy search should find Jonathan
assert html =~ "Jonathan"
assert html =~ "Smith"
assert has_element?(view, "#member-dropdown", "Jonathan")
assert has_element?(view, "#member-dropdown", "Smith")
end
test "search finds member by email", %{conn: conn} do
@ -103,22 +92,17 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Search by email
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "alice.johnson"})
html = render(view)
assert html =~ "Alice"
assert html =~ "Johnson"
assert html =~ "alice.johnson@example.com"
assert has_element?(view, "#member-dropdown", "Alice")
assert has_element?(view, "#member-dropdown", "Johnson")
assert has_element?(view, "#member-dropdown", "alice.johnson@example.com")
end
test "dropdown shows member name and email", %{conn: conn} do
@ -153,11 +137,9 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Bob"})
html = render(view)
assert html =~ "Bob"
assert html =~ "Williams"
assert html =~ "bob@example.com"
assert has_element?(view, "#member-dropdown", "Bob")
assert has_element?(view, "#member-dropdown", "Williams")
assert has_element?(view, "#member-dropdown", "bob@example.com")
end
test "ComboBox hook works (focus opens dropdown)", %{conn: conn} do
@ -177,20 +159,15 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Focus input
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
html = render(view)
# Dropdown should be visible
assert html =~ ~r/role="listbox"/ || html =~ "listbox"
assert has_element?(view, "#member-dropdown[role=listbox]")
end
end
@ -228,21 +205,16 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Search for "David"
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "David"})
# Assert only on dropdown (available members), not the members table
dropdown_html = view |> element("#member-dropdown") |> render()
assert dropdown_html =~ "Anderson"
refute dropdown_html =~ "Miller"
assert has_element?(view, "#member-dropdown", "Anderson")
refute has_element?(view, "#member-dropdown", "Miller")
end
test "search filters correctly when group has many members", %{conn: conn} do
@ -280,23 +252,18 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Search
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Available"})
# Assert only on dropdown (available members), not the members table
dropdown_html = view |> element("#member-dropdown") |> render()
assert dropdown_html =~ "Available"
assert dropdown_html =~ "Member"
refute dropdown_html =~ "Member1"
refute dropdown_html =~ "Member2"
assert has_element?(view, "#member-dropdown", "Available")
assert has_element?(view, "#member-dropdown", "Member")
refute has_element?(view, "#member-dropdown", "Member1")
refute has_element?(view, "#member-dropdown", "Member2")
end
test "search shows no results when all available members are already in group", %{conn: conn} do
@ -321,18 +288,14 @@ defmodule MvWeb.GroupLive.ShowMemberSearchTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Open inline input
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
# Search
# phx-change is on the form, so we need to trigger it via the form
view
|> element("form[phx-change='search_members']")
|> render_change(%{"member_search" => "Only"})
# When no available members, dropdown is not rendered (length(@available_members) == 0)
refute has_element?(view, "#member-dropdown")
end
end

View file

@ -31,19 +31,15 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
actor: system_actor
)
{:ok, view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Member should be in list initially
assert html =~ "Alice"
assert has_element?(view, "[data-testid=group-show-members-table]", "Alice")
# Click Remove button
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Member should no longer be in list (no success flash message)
html = render(view)
refute html =~ "Alice"
refute has_element?(view, "[data-testid=group-show-members-table]", "Alice")
end
test "member is successfully removed from group (verified in list)", %{conn: conn} do
@ -64,20 +60,15 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
actor: system_actor
)
{:ok, view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Member should be in list initially
assert html =~ "Bob"
assert has_element?(view, "[data-testid=group-show-members-table]", "Bob")
# Remove member
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
html = render(view)
# Member should no longer be in list (no success flash message)
refute html =~ "Bob"
refute has_element?(view, "[data-testid=group-show-members-table]", "Bob")
end
test "group member list updates automatically after remove", %{conn: conn} do
@ -98,19 +89,15 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
actor: system_actor
)
{:ok, view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Member should be in list initially
assert html =~ "Charlie"
assert has_element?(view, "[data-testid=group-show-members-table]", "Charlie")
# Remove member
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Member should no longer be in list
html = render(view)
refute html =~ "Charlie"
refute has_element?(view, "[data-testid=group-show-members-table]", "Charlie")
end
test "member count updates automatically after remove", %{conn: conn} do
@ -158,7 +145,7 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
# Extract first member ID from the rendered HTML or use a different approach
# Since we have member1 and member2, we can target member1 specifically
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member1.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member1.id}']")
|> render_click()
# Count should have decreased
@ -187,17 +174,11 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Click Remove - should remove immediately without confirmation
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# No confirmation dialog should appear (immediate removal)
# This is verified by the member being removed without any dialog
# Member should be removed
html = render(view)
refute html =~ "Frank"
refute has_element?(view, "[data-testid=group-show-members-table]", "Frank")
end
end
@ -220,23 +201,17 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
actor: system_actor
)
{:ok, view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Member should be in list
assert html =~ "Grace"
assert has_element?(view, "[data-testid=group-show-members-table]", "Grace")
# Remove last member
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Group should show empty state
assert has_element?(view, "[data-testid=group-show-no-members]")
html = render(view)
assert html =~ gettext("No members in this group") ||
html =~ ~r/no.*members/i
# Count should be 0
count = extract_member_count(html)
assert count == 0
end
@ -269,18 +244,14 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
{:ok, view, _html} = live(conn, "/groups/#{group1.slug}")
# Remove from group1
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Member should be removed from group1
html = render(view)
refute html =~ "Henry"
refute has_element?(view, "[data-testid=group-show-members-table]", "Henry")
# Verify member is still in group2
{:ok, _view2, html2} = live(conn, "/groups/#{group2.slug}")
assert html2 =~ "Henry"
{:ok, view2, _html2} = live(conn, "/groups/#{group2.slug}")
assert has_element?(view2, "[data-testid=group-show-members-table]", "Henry")
end
test "remove is idempotent (no error if member already removed)", %{conn: conn} do
@ -303,22 +274,15 @@ defmodule MvWeb.GroupLive.ShowRemoveMemberTest do
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Remove member first time
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Try to remove again (should not error, just be idempotent)
# Note: Implementation should handle this gracefully
# If button is still visible somehow, try to click again
html = render(view)
if html =~ "Isabel" do
if has_element?(view, "[data-testid=group-show-members-table]", "Isabel") do
view
|> element("button[phx-click='remove_member'][phx-value-member_id='#{member.id}']")
|> element("[data-testid=group-show-remove-member][phx-value-member_id='#{member.id}']")
|> render_click()
# Should not crash
assert render(view)
end
end

View file

@ -22,34 +22,33 @@ defmodule MvWeb.GroupLive.ShowTest do
test "page renders successfully", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
assert has_element?(view, "h1", group.name)
end
test "displays group name", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Test Group Name"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Test Group Name"
assert has_element?(view, "h1", "Test Group Name")
end
test "displays group description when present", %{conn: conn} do
group = Fixtures.group_fixture(%{description: "This is a test description"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ "This is a test description"
assert has_element?(view, "p", "This is a test description")
end
test "displays member count", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Member count should be displayed (might be 0 or more)
assert html =~ "0" or html =~ gettext("Members") or html =~ "member" or html =~ "Mitglied"
assert has_element?(view, "[data-testid=group-show-member-count]")
end
test "displays list of members in group", %{conn: conn} do
@ -67,26 +66,26 @@ defmodule MvWeb.GroupLive.ShowTest do
actor: system_actor
)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Alice" or html =~ "Smith"
assert html =~ "Bob" or html =~ "Jones"
assert has_element?(view, "[data-testid=group-show-members-table]", "Alice")
assert has_element?(view, "[data-testid=group-show-members-table]", "Bob")
end
test "displays edit button for admin users", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ gettext("Edit") or html =~ "edit" or html =~ "Bearbeiten"
assert has_element?(view, "[data-testid=group-show-edit-btn]")
end
test "displays delete button for admin users", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ gettext("Delete") or html =~ "delete" or html =~ "Löschen"
assert has_element?(view, "[data-testid=group-show-delete-btn]")
end
end
@ -94,19 +93,17 @@ defmodule MvWeb.GroupLive.ShowTest do
test "route /groups/:slug works correctly", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Board Members"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Board Members"
# Verify slug is in URL
assert html =~ group.slug or html =~ "board-members"
assert has_element?(view, "h1", "Board Members")
end
test "group is found by slug via unique_slug identity", %{conn: conn} do
group = Fixtures.group_fixture(%{name: "Test Group"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
assert has_element?(view, "h1", group.name)
end
test "non-existent slug returns 404", %{conn: conn} do
@ -145,28 +142,26 @@ defmodule MvWeb.GroupLive.ShowTest do
test "displays empty group correctly (0 members)", %{conn: conn} do
group = Fixtures.group_fixture()
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ "0" or html =~ gettext("No members") or html =~ "empty" or
html =~ "Keine Mitglieder"
assert has_element?(view, "[data-testid=group-show-no-members]")
end
test "handles group without description correctly", %{conn: conn} do
group = Fixtures.group_fixture(%{description: nil})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
# Should not crash, description should be optional
assert html =~ group.name
assert has_element?(view, "h1", group.name)
end
test "handles slug with special characters correctly", %{conn: conn} do
# Create group with name that generates slug with hyphens
group = Fixtures.group_fixture(%{name: "Test-Group-Name"})
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ "Test-Group-Name" or html =~ group.name
assert has_element?(view, "h1", group.name)
end
end
@ -177,11 +172,11 @@ defmodule MvWeb.GroupLive.ShowTest do
read_only_user = Fixtures.user_with_role_fixture("read_only")
conn = conn_with_password_user(conn, read_only_user)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
# Should NOT see edit/delete buttons
refute html =~ gettext("Edit") or html =~ gettext("Delete")
assert has_element?(view, "h1", group.name)
refute has_element?(view, "[data-testid=group-show-edit-btn]")
refute has_element?(view, "[data-testid=group-show-delete-btn]")
end
@tag role: :unauthenticated
@ -246,14 +241,14 @@ defmodule MvWeb.GroupLive.ShowTest do
handler_id = "test-query-counter-#{System.unique_integer([:positive])}"
:telemetry.attach(handler_id, [:ash, :query, :start], handler, nil)
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
final_count = Agent.get(query_count_agent, & &1)
:telemetry.detach(handler_id)
# All members should be displayed
Enum.each(members, fn member ->
assert html =~ member.first_name or html =~ member.last_name
assert has_element?(view, "[data-testid=group-show-members-table]", member.first_name) or
has_element?(view, "[data-testid=group-show-members-table]", member.last_name)
end)
# Verify query count is reasonable (should avoid N+1 queries)
@ -267,10 +262,9 @@ defmodule MvWeb.GroupLive.ShowTest do
test "slug lookup is efficient (uses unique_slug index)", %{conn: conn} do
group = Fixtures.group_fixture()
# Should use index for fast lookup
{:ok, _view, html} = live(conn, "/groups/#{group.slug}")
{:ok, view, _html} = live(conn, "/groups/#{group.slug}")
assert html =~ group.name
assert has_element?(view, "h1", group.name)
end
end

View file

@ -13,7 +13,7 @@ defmodule MvWeb.GroupLiveHelpers do
"""
def open_add_member(view) do
view
|> element("button", "Add Member")
|> element("button[phx-click='show_add_member_input']")
|> render_click()
end
@ -22,7 +22,7 @@ defmodule MvWeb.GroupLiveHelpers do
"""
def search_member(view, query) do
view
|> element("#member-search-input")
|> element("[data-testid=group-show-member-search-input]")
|> render_focus()
view
@ -44,7 +44,7 @@ defmodule MvWeb.GroupLiveHelpers do
"""
def add_selected(view) do
view
|> element("button[phx-click='add_selected_members']")
|> element("[data-testid=group-show-add-selected-members-btn]")
|> render_click()
end