defmodule MvWeb.MemberLive.IndexTest do use MvWeb.ConnCase, async: true import Phoenix.LiveViewTest require Ash.Query test "shows translated title in German", %{conn: conn} do conn = conn_with_oidc_user(conn) conn = Plug.Test.init_test_session(conn, locale: "de") {:ok, _view, html} = live(conn, "/members") # Expected German title assert html =~ "Mitglieder" end test "shows translated title in English", %{conn: conn} do conn = conn_with_oidc_user(conn) Gettext.put_locale(MvWeb.Gettext, "en") {:ok, _view, html} = live(conn, "/members") # Expected English title assert html =~ "Members" end test "shows translated button text in German", %{conn: conn} do conn = conn_with_oidc_user(conn) conn = Plug.Test.init_test_session(conn, locale: "de") {:ok, _view, html} = live(conn, "/members/new") assert html =~ "Speichern" end test "shows translated button text in English", %{conn: conn} do conn = conn_with_oidc_user(conn) Gettext.put_locale(MvWeb.Gettext, "en") {:ok, _view, html} = live(conn, "/members/new") assert html =~ "Save" end test "shows translated flash message after creating a member in German", %{conn: conn} do conn = conn_with_oidc_user(conn) conn = Plug.Test.init_test_session(conn, locale: "de") {:ok, form_view, _html} = live(conn, "/members/new") form_data = %{ "member[first_name]" => "Max", "member[last_name]" => "Mustermann", "member[email]" => "max@example.com" } # Submit form and follow the redirect to get the flash message {:ok, index_view, _html} = form_view |> form("#member-form", form_data) |> render_submit() |> follow_redirect(conn, "/members") assert has_element?(index_view, "#flash-group", "Mitglied erstellt erfolgreich") end test "shows translated flash message after creating a member in English", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, form_view, _html} = live(conn, "/members/new") form_data = %{ "member[first_name]" => "Max", "member[last_name]" => "Mustermann", "member[email]" => "max@example.com" } # Submit form and follow the redirect to get the flash message {:ok, index_view, _html} = form_view |> form("#member-form", form_data) |> render_submit() |> follow_redirect(conn, "/members") assert has_element?(index_view, "#flash-group", "Member create successfully") end describe "sorting integration" do test "clicking a column header toggles sort order and updates the URL", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # The component data test ids are built with the name of the field # First click – should sort ASC view |> element("[data-testid='email']") |> render_click() # The LiveView pushes a patch with the new query params assert_patch(view, "/members?query=&sort_field=email&sort_order=asc") # Second click – toggles to DESC view |> element("[data-testid='email']") |> render_click() assert_patch(view, "/members?query=&sort_field=email&sort_order=desc") end test "clicking different column header resets order to ascending", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?sort_field=email&sort_order=desc") # Click on a different column view |> element("[data-testid='first_name']") |> render_click() assert_patch(view, "/members?query=&sort_field=first_name&sort_order=asc") end test "all sortable columns work correctly", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # default ascending sorting with first name assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']") sortable_fields = [ :email, :street, :house_number, :postal_code, :city, :phone_number, :join_date ] for field <- sortable_fields do view |> element("[data-testid='#{field}']") |> render_click() assert_patch(view, "/members?query=&sort_field=#{field}&sort_order=asc") end end test "sorting works with search query", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=test") view |> element("[data-testid='email']") |> render_click() assert_patch(view, "/members?query=test&sort_field=email&sort_order=asc") end test "sorting maintains search query when toggling order", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=asc") view |> element("[data-testid='email']") |> render_click() assert_patch(view, "/members?query=test&sort_field=email&sort_order=desc") end end describe "URL param handling" do test "handle_params reads sort query and applies it", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc") # Check that the sort state is correctly applied assert has_element?(view, "[data-testid='email'][aria-label='descending']") end test "handle_params handles invalid sort field gracefully", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=&sort_field=invalid_field&sort_order=asc") # Should not crash and should show default first name order assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']") end test "handle_params preserves search query with sort params", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=desc") # Both search and sort should be preserved assert has_element?(view, "[data-testid='email'][aria-label='descending']") end end describe "search and sort integration" do test "search maintains sort state", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc") # Perform search view |> element("[data-testid='search-input']") |> render_change(%{value: "test"}) # Sort state should be maintained assert has_element?(view, "[data-testid='email'][aria-label='descending']") end test "sort maintains search state", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=asc") # Perform sort view |> element("[data-testid='email']") |> render_click() # Search state should be maintained assert_patch(view, "/members?query=test&sort_field=email&sort_order=desc") end end test "handle_info(:search_changed) updates assigns with search results", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") send(view.pid, {:search_changed, "Friedrich"}) state = :sys.get_state(view.pid) assert state.socket.assigns.query == "Friedrich" assert is_list(state.socket.assigns.members) end test "can delete a member without error", %{conn: conn} do # Create a test member first {:ok, member} = Mv.Membership.create_member(%{ first_name: "Test", last_name: "User", email: "test@example.com" }) conn = conn_with_oidc_user(conn) {:ok, index_view, _html} = live(conn, "/members") # Verify the member is displayed assert has_element?(index_view, "#members", "Test User") # Click the delete link for this member index_view |> element("a", "Delete") |> render_click() # Verify the member is no longer displayed refute has_element?(index_view, "#members", "Test User") # Verify the member was actually deleted from the database assert not (Mv.Membership.Member |> Ash.Query.filter(id == ^member.id) |> Ash.exists?()) end describe "copy_emails feature" do setup do # Create test members {:ok, member1} = Mv.Membership.create_member(%{ first_name: "Max", last_name: "Mustermann", email: "max@example.com" }) {:ok, member2} = Mv.Membership.create_member(%{ first_name: "Erika", last_name: "Musterfrau", email: "erika@example.com" }) {:ok, member3} = Mv.Membership.create_member(%{ first_name: "Hans", last_name: "Müller-Lüdenscheidt", email: "hans@example.com" }) %{member1: member1, member2: member2, member3: member3} end test "copy_emails event formats selected members correctly", %{ conn: conn, member1: member1, member2: member2 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Select two members view |> element("[phx-click='select_member'][phx-value-id='#{member1.id}']") |> render_click() view |> element("[phx-click='select_member'][phx-value-id='#{member2.id}']") |> render_click() # Trigger copy_emails event view |> element("#copy-emails-btn") |> render_click() # Verify flash message shows correct count assert render(view) =~ "2" end test "copy_emails event with no selection shows error flash", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Trigger copy_emails event directly (button not visible when no selection) # This tests the edge case where event is triggered without selection result = render_hook(view, "copy_emails", %{}) # Should show error flash assert result =~ "No members selected" or result =~ "Keine Mitglieder" end test "copy_emails event with all members selected formats all emails", %{ conn: conn } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Select all members via select_all view |> element("[phx-click='select_all']") |> render_click() # Trigger copy_emails event view |> element("#copy-emails-btn") |> render_click() # Verify flash message shows correct count (3 members) assert render(view) =~ "3" end test "copy_emails handles members with special characters in names", %{ conn: conn, member3: member3 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Select member with umlauts view |> element("[phx-click='select_member'][phx-value-id='#{member3.id}']") |> render_click() # Trigger copy_emails event - should not crash view |> element("#copy-emails-btn") |> render_click() # Verify flash message shows success assert render(view) =~ "1" end test "copy_emails handles case where selected members are deleted", %{ conn: conn, member1: member1 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Select a member view |> element("[phx-click='select_member'][phx-value-id='#{member1.id}']") |> render_click() # Click copy button - should work correctly view |> element("#copy-emails-btn") |> render_click() # Should show count of actual members found (1) assert render(view) =~ "1" end test "copy button is not visible when no members are selected", %{conn: conn} do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Ensure no members are selected (default state) refute has_element?(view, "#copy-emails-btn") end test "copy button is visible when members are selected", %{ conn: conn, member1: member1 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Select a member view |> element("[phx-click='select_member'][phx-value-id='#{member1.id}']") |> render_click() # Button should now be visible assert has_element?(view, "#copy-emails-btn") end test "copy button click triggers event and shows flash", %{ conn: conn, member1: member1 } do conn = conn_with_oidc_user(conn) {:ok, view, _html} = live(conn, "/members") # Select a member view |> element("[phx-click='select_member'][phx-value-id='#{member1.id}']") |> render_click() # Click copy button view |> element("#copy-emails-btn") |> render_click() # Flash message should appear assert has_element?(view, "#flash-group") end end end