diff --git a/lib/mv_web/live/group_live/show.ex b/lib/mv_web/live/group_live/show.ex index dc0a922..0c7e93e 100644 --- a/lib/mv_web/live/group_live/show.ex +++ b/lib/mv_web/live/group_live/show.ex @@ -98,12 +98,20 @@ defmodule MvWeb.GroupLive.Show do
<%= if can?(@current_user, :update, @group) do %> - <.button variant="primary" navigate={~p"/groups/#{@group.slug}/edit"}> + <.button + variant="primary" + navigate={~p"/groups/#{@group.slug}/edit"} + data-testid="group-show-edit-btn" + > {gettext("Edit")} <% end %> <%= if can?(@current_user, :destroy, @group) do %> - <.button class="btn-error" phx-click="open_delete_modal"> + <.button + class="btn-error" + phx-click="open_delete_modal" + data-testid="group-show-delete-btn" + > {gettext("Delete")} <% end %> @@ -126,7 +134,7 @@ defmodule MvWeb.GroupLive.Show do

{gettext("Members")}

-

+

{ngettext( "Total: %{count} member", "Total: %{count} members", @@ -163,6 +171,7 @@ defmodule MvWeb.GroupLive.Show do @@ -258,9 +268,11 @@ defmodule MvWeb.GroupLive.Show do <% end %> <%= if Enum.empty?(@group.members || []) do %> -

{gettext("No members in this group")}

+

+ {gettext("No members in this group")} +

<% else %> -
+
@@ -301,6 +313,7 @@ defmodule MvWeb.GroupLive.Show do class="btn btn-ghost btn-sm text-error" phx-click="remove_member" phx-value-member_id={member.id} + data-testid="group-show-remove-member" aria-label={gettext("Remove member from group")} data-tooltip={gettext("Remove")} > diff --git a/test/mv_web/live/group_live/form_test.exs b/test/mv_web/live/group_live/form_test.exs index 9169dfe..8934e85 100644 --- a/test/mv_web/live/group_live/form_test.exs +++ b/test/mv_web/live/group_live/form_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/index_test.exs b/test/mv_web/live/group_live/index_test.exs index b972095..751b5c6 100644 --- a/test/mv_web/live/group_live/index_test.exs +++ b/test/mv_web/live/group_live/index_test.exs @@ -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) diff --git a/test/mv_web/live/group_live/integration_test.exs b/test/mv_web/live/group_live/integration_test.exs index a98de85..96e9031 100644 --- a/test/mv_web/live/group_live/integration_test.exs +++ b/test/mv_web/live/group_live/integration_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/show_accessibility_test.exs b/test/mv_web/live/group_live/show_accessibility_test.exs index 97ce469..fc63551 100644 --- a/test/mv_web/live/group_live/show_accessibility_test.exs +++ b/test/mv_web/live/group_live/show_accessibility_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/show_add_member_test.exs b/test/mv_web/live/group_live/show_add_member_test.exs index 783db9d..0e1af32 100644 --- a/test/mv_web/live/group_live/show_add_member_test.exs +++ b/test/mv_web/live/group_live/show_add_member_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/show_add_remove_members_test.exs b/test/mv_web/live/group_live/show_add_remove_members_test.exs index c014372..047205d 100644 --- a/test/mv_web/live/group_live/show_add_remove_members_test.exs +++ b/test/mv_web/live/group_live/show_add_remove_members_test.exs @@ -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/]*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 diff --git a/test/mv_web/live/group_live/show_authorization_test.exs b/test/mv_web/live/group_live/show_authorization_test.exs index f121b36..4bc2a49 100644 --- a/test/mv_web/live/group_live/show_authorization_test.exs +++ b/test/mv_web/live/group_live/show_authorization_test.exs @@ -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/]*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/]*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 diff --git a/test/mv_web/live/group_live/show_integration_test.exs b/test/mv_web/live/group_live/show_integration_test.exs index 0a82be8..13f8e5d 100644 --- a/test/mv_web/live/group_live/show_integration_test.exs +++ b/test/mv_web/live/group_live/show_integration_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/show_member_search_test.exs b/test/mv_web/live/group_live/show_member_search_test.exs index 75d1803..ed8a55d 100644 --- a/test/mv_web/live/group_live/show_member_search_test.exs +++ b/test/mv_web/live/group_live/show_member_search_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/show_remove_member_test.exs b/test/mv_web/live/group_live/show_remove_member_test.exs index d081b50..2b47941 100644 --- a/test/mv_web/live/group_live/show_remove_member_test.exs +++ b/test/mv_web/live/group_live/show_remove_member_test.exs @@ -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 diff --git a/test/mv_web/live/group_live/show_test.exs b/test/mv_web/live/group_live/show_test.exs index bef234b..07a0c98 100644 --- a/test/mv_web/live/group_live/show_test.exs +++ b/test/mv_web/live/group_live/show_test.exs @@ -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 diff --git a/test/support/group_live_helpers.ex b/test/support/group_live_helpers.ex index 50e2f9e..d8f87b8 100644 --- a/test/support/group_live_helpers.ex +++ b/test/support/group_live_helpers.ex @@ -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