From 0b29fbbd21aaa7b2b8b7c2b01ca39ad8c08cedbd Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 29 Jan 2026 12:59:06 +0100 Subject: [PATCH] test: restore removed tests including optimizations --- Justfile | 15 +- test/mv_web/live/user_live/show_test.exs | 47 +++++- test/mv_web/user_live/form_test.exs | 163 +++++++++++++++++++- test/mv_web/user_live/index_test.exs | 188 ++++++++++++++++++++++- 4 files changed, 403 insertions(+), 10 deletions(-) diff --git a/Justfile b/Justfile index 6337d7c..bce8bf6 100644 --- a/Justfile +++ b/Justfile @@ -41,18 +41,27 @@ audit: mix deps.audit mix hex.audit +# Run all tests test *args: install-dependencies mix test {{args}} -# Run only fast tests (excludes slow/performance tests) +# Run only fast tests (excludes slow/performance and UI tests) test-fast *args: install-dependencies - mix test --exclude slow {{args}} + mix test --exclude slow --exclude ui {{args}} + +# Run only UI tests +ui *args: install-dependencies + mix test --only ui {{args}} # Run only slow/performance tests +slow *args: install-dependencies + mix test --only slow {{args}} + +# Run only slow/performance tests (alias for consistency) test-slow *args: install-dependencies mix test --only slow {{args}} -# Run all tests (fast + slow) +# Run all tests (fast + slow + ui) test-all *args: install-dependencies mix test {{args}} diff --git a/test/mv_web/live/user_live/show_test.exs b/test/mv_web/live/user_live/show_test.exs index ccea401..fbd0407 100644 --- a/test/mv_web/live/user_live/show_test.exs +++ b/test/mv_web/live/user_live/show_test.exs @@ -23,12 +23,14 @@ defmodule MvWeb.UserLive.ShowTest do end describe "mount and display" do - @tag :slow - test "mounts successfully with valid user ID", %{conn: conn, user: user} do + @tag :ui + test "mounts successfully and displays user information", %{conn: conn, user: user} do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, ~p"/users/#{user.id}") + # Basic display assert html =~ to_string(user.email) + assert html =~ gettext("Email") end test "displays password authentication status when enabled", %{conn: conn} do @@ -96,6 +98,47 @@ defmodule MvWeb.UserLive.ShowTest do end end + describe "navigation" do + @tag :ui + test "navigation buttons work correctly", %{conn: conn, user: user} do + conn = conn_with_oidc_user(conn) + {:ok, view, _html} = live(conn, ~p"/users/#{user.id}") + + # Back button navigates to user list + assert {:error, {:live_redirect, %{to: to}}} = + view + |> element( + "a[aria-label='#{gettext("Back to users list")}'], button[aria-label='#{gettext("Back to users list")}']" + ) + |> render_click() + + assert to == "/users" + + # Edit button navigates to edit form + {:ok, view, _html} = live(conn, ~p"/users/#{user.id}") + + assert {:error, {:live_redirect, %{to: to}}} = + view + |> element( + "a[href='/users/#{user.id}/edit?return_to=show'], button[href='/users/#{user.id}/edit?return_to=show']" + ) + |> render_click() + + assert to == "/users/#{user.id}/edit?return_to=show" + end + end + + describe "page title" do + @tag :ui + test "sets correct page title", %{conn: conn, user: user} do + conn = conn_with_oidc_user(conn) + {:ok, _view, html} = live(conn, ~p"/users/#{user.id}") + + # Check that page title is set (might be in title tag or header) + assert html =~ gettext("Show User") || html =~ to_string(user.email) + end + end + describe "error handling" do test "raises exception for invalid user ID", %{conn: conn} do invalid_id = Ecto.UUID.generate() diff --git a/test/mv_web/user_live/form_test.exs b/test/mv_web/user_live/form_test.exs index 842dc98..5a67d75 100644 --- a/test/mv_web/user_live/form_test.exs +++ b/test/mv_web/user_live/form_test.exs @@ -10,15 +10,29 @@ defmodule MvWeb.UserLive.FormTest do end describe "new user form - display" do - test "shows correct form elements", %{conn: conn} do + @tag :ui + test "shows correct form elements and password field toggling", %{conn: conn} do {:ok, view, html} = setup_live_view(conn, "/users/new") + # Basic form elements assert html =~ "New User" assert html =~ "Email" assert html =~ "Set Password" assert has_element?(view, "form#user-form[phx-submit='save']") assert has_element?(view, "input[name='user[email]']") assert has_element?(view, "input[type='checkbox'][name='set_password']") + + # Password fields should be hidden initially + refute has_element?(view, "input[name='user[password]']") + refute has_element?(view, "input[name='user[password_confirmation]']") + + # Toggle password fields + view |> element("input[name='set_password']") |> render_click() + + # Password fields should now be visible + assert has_element?(view, "input[name='user[password]']") + assert has_element?(view, "input[name='user[password_confirmation]']") + assert render(view) =~ "Password requirements" end end @@ -71,17 +85,89 @@ defmodule MvWeb.UserLive.FormTest do assert to_string(user.email) == "storetest@example.com" assert is_nil(user.hashed_password) end + + test "stores password when provided", %{conn: conn} do + {:ok, view, _html} = setup_live_view(conn, "/users/new") + + view |> element("input[name='set_password']") |> render_click() + + view + |> form("#user-form", + user: %{ + email: "passwordstoretest@example.com", + password: "securepassword123", + password_confirmation: "securepassword123" + } + ) + |> render_submit() + + system_actor = Mv.Helpers.SystemActor.get_system_actor() + + user = + Ash.get!( + Mv.Accounts.User, + [email: Ash.CiString.new("passwordstoretest@example.com")], + domain: Mv.Accounts, + actor: system_actor + ) + + assert user.hashed_password != nil + refute is_nil(user.hashed_password) + end + end + + describe "new user form - validation" do + test "shows error for duplicate email", %{conn: conn} do + _existing_user = create_test_user(%{email: "existing@example.com"}) + {:ok, view, _html} = setup_live_view(conn, "/users/new") + + html = + view + |> form("#user-form", user: %{email: "existing@example.com"}) + |> render_submit() + + assert html =~ "has already been taken" + end + + test "shows error for short password", %{conn: conn} do + {:ok, view, _html} = setup_live_view(conn, "/users/new") + + view |> element("input[name='set_password']") |> render_click() + + html = + view + |> form("#user-form", + user: %{ + email: "test@example.com", + password: "123", + password_confirmation: "123" + } + ) + |> render_submit() + + assert html =~ "length must be greater than or equal to 8" + end end describe "edit user form - display" do - test "shows correct form elements for existing user", %{conn: conn} do + @tag :ui + test "shows correct form elements and admin password fields", %{conn: conn} do user = create_test_user(%{email: "editme@example.com"}) {:ok, view, html} = setup_live_view(conn, "/users/#{user.id}/edit") + # Basic form elements assert html =~ "Edit User" assert html =~ "Change Password" assert has_element?(view, "input[name='user[email]'][value='editme@example.com']") assert html =~ "Check 'Change Password' above to set a new password for this user" + + # Toggle admin password fields + view |> element("input[name='set_password']") |> render_click() + + # Admin password fields should be visible (no confirmation field for admin) + assert has_element?(view, "input[name='user[password]']") + refute has_element?(view, "input[name='user[password_confirmation]']") + assert render(view) =~ "Admin Note" end end @@ -129,6 +215,46 @@ defmodule MvWeb.UserLive.FormTest do end end + describe "edit user form - validation" do + test "shows error for duplicate email", %{conn: conn} do + _existing_user = create_test_user(%{email: "taken@example.com"}) + user_to_edit = create_test_user(%{email: "original@example.com"}) + {:ok, view, _html} = setup_live_view(conn, "/users/#{user_to_edit.id}/edit") + + html = + view + |> form("#user-form", user: %{email: "taken@example.com"}) + |> render_submit() + + assert html =~ "has already been taken" + end + + test "shows error for invalid password", %{conn: conn} do + user = create_test_user(%{email: "user@example.com"}) + {:ok, view, _html} = setup_live_view(conn, "/users/#{user.id}/edit") + + view |> element("input[name='set_password']") |> render_click() + + result = + view + |> form("#user-form", + user: %{ + email: "user@example.com", + password: "123" + } + ) + |> render_submit() + + case result do + {:error, {:live_redirect, %{to: "/users"}}} -> + flunk("Expected validation error but form was submitted successfully") + + html when is_binary(html) -> + assert html =~ "must have length of at least 8" + end + end + end + describe "member linking - display" do test "shows linked member with unlink button when user has member", %{conn: conn} do system_actor = Mv.Helpers.SystemActor.get_system_actor() @@ -259,6 +385,39 @@ defmodule MvWeb.UserLive.FormTest do end end + describe "internationalization" do + @tag :ui + test "shows translated labels in different locales", %{conn: conn} do + # Test German labels + conn = conn_with_oidc_user(conn, %{email: "admin_de@example.com"}) + conn = Plug.Test.init_test_session(conn, locale: "de") + {:ok, _view, html_de} = live(conn, "/users/new") + + assert html_de =~ "Neue*r Benutzer*in" + assert html_de =~ "E-Mail" + assert html_de =~ "Passwort setzen" + + # Test English labels + conn = conn_with_oidc_user(conn, %{email: "admin_en@example.com"}) + Gettext.put_locale(MvWeb.Gettext, "en") + {:ok, _view, html_en} = live(conn, "/users/new") + + assert html_en =~ "New User" + assert html_en =~ "Email" + assert html_en =~ "Set Password" + + # Test different labels for edit vs new + user = create_test_user(%{email: "test@example.com"}) + conn = conn_with_oidc_user(conn, %{email: "admin@example.com"}) + + {:ok, _view, new_html} = live(conn, "/users/new") + {:ok, _view, edit_html} = live(conn, "/users/#{user.id}/edit") + + assert new_html =~ "Set Password" + assert edit_html =~ "Change Password" + end + end + describe "system actor user" do test "redirects to user list when editing system actor user", %{conn: conn} do system_actor = Mv.Helpers.SystemActor.get_system_actor() diff --git a/test/mv_web/user_live/index_test.exs b/test/mv_web/user_live/index_test.exs index 1b470c1..2f4a53f 100644 --- a/test/mv_web/user_live/index_test.exs +++ b/test/mv_web/user_live/index_test.exs @@ -3,17 +3,38 @@ defmodule MvWeb.UserLive.IndexTest do import Phoenix.LiveViewTest describe "basic functionality" do - @tag :slow - test "displays users in a table", %{conn: conn} do + @tag :ui + test "displays users in a table with basic UI elements", %{conn: conn} do # Create test users - _user1 = create_test_user(%{email: "alice@example.com", oidc_id: "alice123"}) + user1 = create_test_user(%{email: "alice@example.com", oidc_id: "alice123"}) _user2 = create_test_user(%{email: "bob@example.com", oidc_id: "bob456"}) conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/users") + # Basic table rendering assert html =~ "alice@example.com" assert html =~ "bob@example.com" + + # UI elements: New User button, action links + assert html =~ "New User" + assert html =~ "Edit" + assert html =~ "Delete" + assert html =~ ~r/href="[^"]*\/users\/#{user1.id}\/edit"/ + end + + @tag :ui + test "shows translated titles in different locales", %{conn: conn} do + # Test German translation + conn = conn_with_oidc_user(conn) + conn = Plug.Test.init_test_session(conn, locale: "de") + {:ok, _view, html_de} = live(conn, "/users") + assert html_de =~ "Benutzer*innen auflisten" + + # Test English translation + Gettext.put_locale(MvWeb.Gettext, "en") + {:ok, _view, html_en} = live(conn, "/users") + assert html_en =~ "Listing Users" end end @@ -68,6 +89,34 @@ defmodule MvWeb.UserLive.IndexTest do assert mike_pos < alpha_pos, "mike@example.com should appear before alpha@example.com when sorted desc" end + + @tag :ui + test "toggles sort direction and shows correct icons", %{conn: conn} do + conn = conn_with_oidc_user(conn) + {:ok, view, _html} = live(conn, "/users") + + # Initially ascending - should show up arrow + html = render(view) + assert html =~ "hero-chevron-up" + + # After clicking, should show down arrow + view |> element("button[phx-value-field='email']") |> render_click() + html = render(view) + assert html =~ "hero-chevron-down" + + # Click again to toggle back to ascending + html = view |> element("button[phx-value-field='email']") |> render_click() + assert html =~ "hero-chevron-up" + assert html =~ ~s(aria-sort="ascending") + + # Should be back to original ascending order + alpha_pos = html |> :binary.match("alpha@example.com") |> elem(0) + mike_pos = html |> :binary.match("mike@example.com") |> elem(0) + zulu_pos = html |> :binary.match("zulu@example.com") |> elem(0) + + assert alpha_pos < mike_pos, "Should be back to ascending: alpha before mike" + assert mike_pos < zulu_pos, "Should be back to ascending: mike before zulu" + end end describe "checkbox selection functionality" do @@ -77,6 +126,102 @@ defmodule MvWeb.UserLive.IndexTest do %{users: [user1, user2]} end + @tag :ui + test "shows checkbox UI elements", %{conn: conn, users: [user1, user2]} do + conn = conn_with_oidc_user(conn) + {:ok, _view, html} = live(conn, "/users") + + # Check select all checkbox exists + assert html =~ ~s(name="select_all") + assert html =~ ~s(phx-click="select_all") + + # Check individual user checkboxes exist + assert html =~ ~s(name="#{user1.id}") + assert html =~ ~s(name="#{user2.id}") + assert html =~ ~s(phx-click="select_user") + end + + @tag :ui + test "can select and deselect individual users", %{conn: conn, users: [user1, user2]} do + conn = conn_with_oidc_user(conn) + {:ok, view, _html} = live(conn, "/users") + + # Initially, individual checkboxes should exist but not be checked + assert view |> element("input[type='checkbox'][name='#{user1.id}']") |> has_element?() + assert view |> element("input[type='checkbox'][name='#{user2.id}']") |> has_element?() + + refute view + |> element("input[type='checkbox'][name='select_all'][checked]") + |> has_element?() + + # Select first user checkbox + html = view |> element("input[type='checkbox'][name='#{user1.id}']") |> render_click() + assert html =~ "Email" + assert html =~ to_string(user1.email) + + # The select_all checkbox should still not be checked (not all users selected) + refute view + |> element("input[type='checkbox'][name='select_all'][checked]") + |> has_element?() + + # Deselect user + html = view |> element("input[type='checkbox'][name='#{user1.id}']") |> render_click() + assert html =~ "Email" + + refute view + |> element("input[type='checkbox'][name='select_all'][checked]") + |> has_element?() + end + + @tag :ui + test "select all and deselect all functionality", %{conn: conn, users: [user1, user2]} do + conn = conn_with_oidc_user(conn) + {:ok, view, _html} = live(conn, "/users") + + # Initially no checkboxes should be checked + refute view + |> element("input[type='checkbox'][name='select_all'][checked]") + |> has_element?() + + refute view + |> element("input[type='checkbox'][name='#{user1.id}'][checked]") + |> has_element?() + + refute view + |> element("input[type='checkbox'][name='#{user2.id}'][checked]") + |> has_element?() + + # Click select all + html = view |> element("input[type='checkbox'][name='select_all']") |> render_click() + + # After selecting all, the select_all checkbox should be checked + assert view + |> element("input[type='checkbox'][name='select_all'][checked]") + |> has_element?() + + assert html =~ "Email" + assert html =~ to_string(user1.email) + assert html =~ to_string(user2.email) + + # Then deselect all + html = view |> element("input[type='checkbox'][name='select_all']") |> render_click() + + # After deselecting all, no checkboxes should be checked + refute view + |> element("input[type='checkbox'][name='select_all'][checked]") + |> has_element?() + + refute view + |> element("input[type='checkbox'][name='#{user1.id}'][checked]") + |> has_element?() + + refute view + |> element("input[type='checkbox'][name='#{user2.id}'][checked]") + |> has_element?() + + assert html =~ "Email" + end + @tag :slow test "select all automatically checks when all individual users are selected", %{ conn: conn, @@ -136,6 +281,43 @@ defmodule MvWeb.UserLive.IndexTest do end end + describe "navigation" do + @tag :ui + test "navigation links point to correct pages", %{conn: conn} do + user = create_test_user(%{email: "navigate@example.com"}) + conn = conn_with_oidc_user(conn) + {:ok, view, html} = live(conn, "/users") + + # Check that user row contains link to show page + assert html =~ ~s(/users/#{user.id}) + + # Check edit link points to correct edit page + assert html =~ ~s(/users/#{user.id}/edit) + + # Check new user button points to correct new page + assert html =~ ~s(/users/new) + end + end + + describe "translations" do + @tag :ui + test "shows translations for selection in different locales", %{conn: conn} do + conn = conn_with_oidc_user(conn) + + # Test German translations + conn = Plug.Test.init_test_session(conn, locale: "de") + {:ok, _view, html_de} = live(conn, "/users") + assert html_de =~ "Alle Benutzer*innen auswählen" + assert html_de =~ "Benutzer*in auswählen" + + # Test English translations + Gettext.put_locale(MvWeb.Gettext, "en") + {:ok, _view, html_en} = live(conn, "/users") + # Check that aria-label attributes exist (structure is there) + assert html_en =~ ~s(aria-label=) + end + end + describe "edge cases" do test "handles empty user list gracefully", %{conn: conn} do # Don't create any users besides the authenticated one