Merge branch 'main' into feat/299_plz
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
63040afee7
68 changed files with 4858 additions and 743 deletions
|
|
@ -86,6 +86,66 @@ defmodule Mv.Membership.MemberTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Settings-driven required fields" do
|
||||
@valid_attrs %{
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email: "john@example.com"
|
||||
}
|
||||
|
||||
setup do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
saved_visibility = settings.member_field_visibility || %{}
|
||||
saved_required = settings.member_field_required || %{}
|
||||
|
||||
on_exit(fn ->
|
||||
{:ok, s} = Membership.get_settings()
|
||||
|
||||
Membership.update_settings(s, %{
|
||||
member_field_visibility: saved_visibility,
|
||||
member_field_required: saved_required
|
||||
})
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "when first_name is required in settings, create without first_name fails", %{
|
||||
actor: actor
|
||||
} do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
{:ok, _} =
|
||||
Membership.update_single_member_field(settings,
|
||||
field: "first_name",
|
||||
show_in_overview: true,
|
||||
required: true
|
||||
)
|
||||
|
||||
attrs = Map.delete(@valid_attrs, :first_name)
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Membership.create_member(attrs, actor: actor)
|
||||
|
||||
assert error_message(errors, :first_name) =~ "can't be blank"
|
||||
end
|
||||
|
||||
test "when first_name is required in settings, create with first_name succeeds", %{
|
||||
actor: actor
|
||||
} do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
{:ok, _} =
|
||||
Membership.update_single_member_field(settings,
|
||||
field: "first_name",
|
||||
show_in_overview: true,
|
||||
required: true
|
||||
)
|
||||
|
||||
assert {:ok, _member} = Membership.create_member(@valid_attrs, actor: actor)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Authorization" do
|
||||
@valid_attrs %{
|
||||
first_name: "John",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,23 @@ defmodule Mv.Membership.SettingTest do
|
|||
alias Mv.Membership
|
||||
|
||||
describe "Settings Resource" do
|
||||
setup do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
saved_visibility = settings.member_field_visibility || %{}
|
||||
saved_required = settings.member_field_required || %{}
|
||||
|
||||
on_exit(fn ->
|
||||
{:ok, s} = Membership.get_settings()
|
||||
|
||||
Membership.update_settings(s, %{
|
||||
member_field_visibility: saved_visibility,
|
||||
member_field_required: saved_required
|
||||
})
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "can read settings" do
|
||||
# Settings should be a singleton resource
|
||||
assert {:ok, _settings} = Membership.get_settings()
|
||||
|
|
@ -39,6 +56,65 @@ defmodule Mv.Membership.SettingTest do
|
|||
|
||||
assert error_message(errors, :club_name) =~ "must be present"
|
||||
end
|
||||
|
||||
test "can update and read member_field_required" do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
required_config = %{"first_name" => true, "last_name" => true}
|
||||
|
||||
assert {:ok, updated} =
|
||||
Membership.update_settings(settings, %{member_field_required: required_config})
|
||||
|
||||
assert updated.member_field_required["first_name"] == true
|
||||
assert updated.member_field_required["last_name"] == true
|
||||
end
|
||||
|
||||
test "member_field_required rejects invalid keys" do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Membership.update_settings(settings, %{
|
||||
member_field_required: %{"invalid_field" => true}
|
||||
})
|
||||
|
||||
assert error_message(errors, :member_field_required) =~ "Invalid member field"
|
||||
end
|
||||
|
||||
test "member_field_required rejects non-boolean values" do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Membership.update_settings(settings, %{
|
||||
member_field_required: %{"first_name" => "yes"}
|
||||
})
|
||||
|
||||
assert error_message(errors, :member_field_required) =~ "must be booleans"
|
||||
end
|
||||
|
||||
test "update_single_member_field updates both visibility and required" do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
assert {:ok, updated} =
|
||||
Membership.update_single_member_field(settings,
|
||||
field: "first_name",
|
||||
show_in_overview: true,
|
||||
required: true
|
||||
)
|
||||
|
||||
assert updated.member_field_visibility["first_name"] == true
|
||||
assert updated.member_field_required["first_name"] == true
|
||||
|
||||
# Update same field to required: false
|
||||
assert {:ok, updated2} =
|
||||
Membership.update_single_member_field(updated,
|
||||
field: "first_name",
|
||||
show_in_overview: false,
|
||||
required: false
|
||||
)
|
||||
|
||||
assert updated2.member_field_visibility["first_name"] == false
|
||||
assert updated2.member_field_required["first_name"] == false
|
||||
end
|
||||
end
|
||||
|
||||
# Helper function to extract error messages
|
||||
|
|
|
|||
83
test/mv/config_vereinfacht_test.exs
Normal file
83
test/mv/config_vereinfacht_test.exs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
defmodule Mv.ConfigVereinfachtTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Config Vereinfacht-related helpers.
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
describe "vereinfacht_env_configured?/0" do
|
||||
test "returns false when no Vereinfacht ENV variables are set" do
|
||||
clear_vereinfacht_env()
|
||||
refute Mv.Config.vereinfacht_env_configured?()
|
||||
end
|
||||
|
||||
test "returns true when VEREINFACHT_API_URL is set" do
|
||||
set_vereinfacht_env("VEREINFACHT_API_URL", "https://api.example.com")
|
||||
assert Mv.Config.vereinfacht_env_configured?()
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
|
||||
test "returns true when VEREINFACHT_CLUB_ID is set" do
|
||||
set_vereinfacht_env("VEREINFACHT_CLUB_ID", "2")
|
||||
assert Mv.Config.vereinfacht_env_configured?()
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
end
|
||||
|
||||
describe "vereinfacht_configured?/0" do
|
||||
test "returns false when no config is set" do
|
||||
clear_vereinfacht_env()
|
||||
# Settings may have nil for vereinfacht fields
|
||||
refute Mv.Config.vereinfacht_configured?()
|
||||
end
|
||||
end
|
||||
|
||||
describe "vereinfacht_contact_view_url/1" do
|
||||
test "returns nil when API URL is not configured" do
|
||||
clear_vereinfacht_env()
|
||||
assert Mv.Config.vereinfacht_contact_view_url("123") == nil
|
||||
end
|
||||
|
||||
test "returns app contact view URL when API URL is set (derived app URL)" do
|
||||
clear_vereinfacht_env()
|
||||
clear_vereinfacht_app_url_from_settings()
|
||||
set_vereinfacht_env("VEREINFACHT_API_URL", "https://api.example.com/api/v1")
|
||||
|
||||
assert Mv.Config.vereinfacht_contact_view_url("42") ==
|
||||
"https://app.example.com/en/admin/finances/contacts/42"
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
|
||||
test "returns app contact view URL when VEREINFACHT_APP_URL is set" do
|
||||
set_vereinfacht_env("VEREINFACHT_APP_URL", "https://app.verein.visuel.dev")
|
||||
|
||||
assert Mv.Config.vereinfacht_contact_view_url("abc") ==
|
||||
"https://app.verein.visuel.dev/en/admin/finances/contacts/abc"
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
end
|
||||
|
||||
defp set_vereinfacht_env(key, value) do
|
||||
System.put_env(key, value)
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
System.delete_env("VEREINFACHT_APP_URL")
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_app_url_from_settings do
|
||||
case Mv.Membership.get_settings() do
|
||||
{:ok, settings} ->
|
||||
Mv.Membership.update_settings(settings, %{vereinfacht_app_url: nil})
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
102
test/mv/vereinfacht/changes/sync_contact_test.exs
Normal file
102
test/mv/vereinfacht/changes/sync_contact_test.exs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
defmodule Mv.Vereinfacht.Changes.SyncContactTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Vereinfacht.Changes.SyncContact.
|
||||
|
||||
When Vereinfacht is not configured, member create/update should succeed
|
||||
and vereinfacht_contact_id remains nil.
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Membership
|
||||
|
||||
setup do
|
||||
clear_vereinfacht_env()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "member create when Vereinfacht not configured" do
|
||||
test "member is created and vereinfacht_contact_id is nil" do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
attrs = %{
|
||||
first_name: "Sync",
|
||||
last_name: "Test",
|
||||
email: "sync_test_#{System.unique_integer([:positive])}@example.com"
|
||||
}
|
||||
|
||||
assert {:ok, member} = Membership.create_member(attrs, actor: system_actor)
|
||||
assert member.vereinfacht_contact_id == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "member update when Vereinfacht not configured" do
|
||||
test "member is updated and vereinfacht_contact_id is unchanged" do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
assert {:ok, updated} =
|
||||
Membership.update_member(member, %{first_name: "Updated"}, actor: system_actor)
|
||||
|
||||
assert updated.vereinfacht_contact_id == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "when Vereinfacht is configured" do
|
||||
# Regression: after_transaction callback receives 2 args (changeset, result), not 3.
|
||||
# If the callback had arity 3, create_member would raise BadArityError.
|
||||
# Also: Client must send JSON-encoded body (iodata); raw map causes ArgumentError
|
||||
# when the request is sent. With an unreachable URL we get :econnrefused before
|
||||
# that, so this test would not catch the iodata bug; a Bypass/stub server would.
|
||||
test "create_member succeeds and after_transaction runs without error (API may fail)" do
|
||||
set_vereinfacht_env()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
attrs = %{
|
||||
first_name: "API",
|
||||
last_name: "Test",
|
||||
email: "api_test_#{System.unique_integer([:positive])}@example.com",
|
||||
street: "Test St",
|
||||
postal_code: "12345",
|
||||
city: "Test City"
|
||||
}
|
||||
|
||||
assert {:ok, member} = Membership.create_member(attrs, actor: system_actor)
|
||||
assert member.id
|
||||
# Sync may fail (e.g. connection refused), so contact_id can stay nil
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
|
||||
test "update_member succeeds and after_transaction runs without error (API may fail)" do
|
||||
set_vereinfacht_env()
|
||||
|
||||
member =
|
||||
Mv.Fixtures.member_fixture(%{
|
||||
street: "Test St",
|
||||
postal_code: "12345",
|
||||
city: "Test City"
|
||||
})
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
assert {:ok, updated} =
|
||||
Membership.update_member(member, %{first_name: "Updated"}, actor: system_actor)
|
||||
|
||||
assert updated.id == member.id
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
end
|
||||
|
||||
defp set_vereinfacht_env do
|
||||
System.put_env("VEREINFACHT_API_URL", "http://127.0.0.1:1/api/v1")
|
||||
System.put_env("VEREINFACHT_API_KEY", "test-key")
|
||||
System.put_env("VEREINFACHT_CLUB_ID", "2")
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
50
test/mv/vereinfacht/client_test.exs
Normal file
50
test/mv/vereinfacht/client_test.exs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
defmodule Mv.Vereinfacht.ClientTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Vereinfacht.Client.
|
||||
|
||||
Only tests the "not configured" path; no real HTTP calls. Config reads from
|
||||
ENV first, then from Settings (DB), so we use DataCase so get_settings() is available.
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Vereinfacht.Client
|
||||
|
||||
setup do
|
||||
clear_vereinfacht_env()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "create_contact/1" do
|
||||
test "returns {:error, :not_configured} when Vereinfacht is not configured" do
|
||||
member = build_member_struct()
|
||||
|
||||
assert Client.create_contact(member) == {:error, :not_configured}
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_contact/2" do
|
||||
test "returns {:error, :not_configured} when Vereinfacht is not configured" do
|
||||
member = build_member_struct()
|
||||
|
||||
assert Client.update_contact("123", member) == {:error, :not_configured}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_member_struct do
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
email: "test@example.com",
|
||||
street: "Street 1",
|
||||
house_number: "2",
|
||||
postal_code: "12345",
|
||||
city: "Berlin"
|
||||
}
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
59
test/mv/vereinfacht/vereinfacht_test.exs
Normal file
59
test/mv/vereinfacht/vereinfacht_test.exs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
defmodule Mv.VereinfachtTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Vereinfacht business logic.
|
||||
|
||||
No real API calls; tests "not configured" path and pure helpers (format_error).
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Vereinfacht
|
||||
|
||||
setup do
|
||||
clear_vereinfacht_env()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "sync_member/1" do
|
||||
test "returns :ok when Vereinfacht is not configured (no-op)" do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
|
||||
assert Vereinfacht.sync_member(member) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "sync_members_without_contact/0" do
|
||||
test "returns {:error, :not_configured} when Vereinfacht is not configured" do
|
||||
assert Vereinfacht.sync_members_without_contact() == {:error, :not_configured}
|
||||
end
|
||||
end
|
||||
|
||||
describe "format_error/1" do
|
||||
test "formats HTTP error with detail" do
|
||||
assert Vereinfacht.format_error({:http, 422, "The email field is required."}) ==
|
||||
"Vereinfacht: The email field is required."
|
||||
end
|
||||
|
||||
test "formats HTTP error without detail" do
|
||||
assert Vereinfacht.format_error({:http, 500, nil}) ==
|
||||
"Vereinfacht: API error (HTTP 500)."
|
||||
end
|
||||
|
||||
test "formats request_failed" do
|
||||
assert Vereinfacht.format_error({:request_failed, %{reason: :econnrefused}}) ==
|
||||
"Vereinfacht: Request failed (e.g. connection error)."
|
||||
end
|
||||
|
||||
test "formats invalid_response and other terms" do
|
||||
assert Vereinfacht.format_error({:invalid_response, %{}}) ==
|
||||
"Vereinfacht: Invalid API response."
|
||||
|
||||
assert Vereinfacht.format_error(:timeout) == "Vereinfacht: :timeout"
|
||||
end
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
29
test/mv/vereinfacht/vereinfacht_test_README.md
Normal file
29
test/mv/vereinfacht/vereinfacht_test_README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Vereinfacht tests – scope and rationale
|
||||
|
||||
## Constraint: no real API in CI
|
||||
|
||||
Tests do **not** call the real Vereinfacht API or a shared test endpoint. All tests use dummy data and either:
|
||||
|
||||
- Assert behaviour when **Vereinfacht is not configured** (ENV + Settings unset), or
|
||||
- Run the **full Member/User flow** with a **unreachable URL** (e.g. `http://127.0.0.1:1`) so the HTTP client fails fast (e.g. `:econnrefused`) and we only assert that the application path does not crash.
|
||||
|
||||
## What the tests cover
|
||||
|
||||
| Test file | What it tests | Why it’s enough without an API |
|
||||
|-----------|----------------|---------------------------------|
|
||||
| **ConfigVereinfachtTest** | `vereinfacht_env_configured?`, `vereinfacht_configured?`, `vereinfacht_contact_view_url` with ENV set/cleared | Pure config logic; no HTTP. |
|
||||
| **ClientTest** | `create_contact/1` and `update_contact/2` return `{:error, :not_configured}` when nothing is configured | Ensures the client does not call Req when config is missing. |
|
||||
| **VereinfachtTest** | `sync_members_without_contact/0` returns `{:error, :not_configured}` when not configured | Ensures bulk sync is a no-op when config is missing. |
|
||||
| **SyncContactTest** | Member create/update with SyncContact change: not configured → no sync; configured with bad URL → action still succeeds, sync may fail | Ensures the Ash change and after_transaction arity are correct and the action result is not broken by sync failures. |
|
||||
|
||||
## What is *not* tested (and would need a stub or real endpoint)
|
||||
|
||||
- Actual HTTP request shape (body, headers) and response handling (201/200, error codes).
|
||||
- Persistence of `vereinfacht_contact_id` after a successful create.
|
||||
- Translation of specific API error payloads into user messages.
|
||||
|
||||
Those would require either a **Bypass** (or similar) stub in front of Req or a dedicated test endpoint; both are out of scope for the current “no real API” setup.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Given the constraint that the API is not called in CI, the tests are **meaningful**: they cover config, “not configured” paths, and integration of SyncContact with Member create/update without crashing. They are **sufficient** for regression safety and refactoring; extending them with a Bypass stub would be an optional next step if we want to assert on request/response shape without hitting the real API.
|
||||
|
|
@ -71,4 +71,18 @@ defmodule MvWeb.GlobalSettingsLiveConfigTest do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Vereinfacht Integration section" do
|
||||
setup %{conn: conn} do
|
||||
admin_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = MvWeb.ConnCase.conn_with_password_user(conn, admin_user)
|
||||
{:ok, conn: conn}
|
||||
end
|
||||
|
||||
@tag :ui
|
||||
test "settings page shows Vereinfacht Integration section", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
assert html =~ "Vereinfacht"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
|
|||
Tests cover:
|
||||
- Rendering all member fields from Mv.Constants.member_fields()
|
||||
- Displaying show_in_overview status as badge (Yes/No)
|
||||
- Displaying required status for required fields (first_name, last_name, email)
|
||||
- Current status is displayed based on settings.member_field_visibility
|
||||
- Displaying required status from settings.member_field_required (email is always required)
|
||||
- Current status is displayed based on settings.member_field_visibility and member_field_required
|
||||
- Default status is "Yes" (visible) when not configured in settings
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
|
|
@ -45,11 +45,10 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
|
|||
assert html =~ "badge" or html =~ "Yes" or html =~ "No"
|
||||
end
|
||||
|
||||
test "displays required status for required fields", %{conn: conn} do
|
||||
test "displays required status column", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# Required fields: first_name, last_name, email
|
||||
# Should have "Required" column or indicator
|
||||
# Should have "Required" column; email is always required
|
||||
assert html =~ "Required" or html =~ "required"
|
||||
end
|
||||
|
||||
|
|
@ -85,40 +84,54 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
|
|||
end
|
||||
|
||||
describe "required fields" do
|
||||
test "marks first_name as required", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
setup do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
saved_visibility = settings.member_field_visibility || %{}
|
||||
saved_required = settings.member_field_required || %{}
|
||||
|
||||
# first_name should be marked as required
|
||||
assert html =~ "first_name" or html =~ "First name"
|
||||
# Should have required indicator
|
||||
assert html =~ "required" or html =~ "Required"
|
||||
on_exit(fn ->
|
||||
{:ok, s} = Membership.get_settings()
|
||||
|
||||
Membership.update_settings(s, %{
|
||||
member_field_visibility: saved_visibility,
|
||||
member_field_required: saved_required
|
||||
})
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "marks last_name as required", %{conn: conn} do
|
||||
test "marks email as required (always from settings)", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# last_name should be marked as required
|
||||
assert html =~ "last_name" or html =~ "Last name"
|
||||
# Should have required indicator
|
||||
assert html =~ "required" or html =~ "Required"
|
||||
end
|
||||
|
||||
test "marks email as required", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# email should be marked as required
|
||||
# Email is always required
|
||||
assert html =~ "email" or html =~ "Email"
|
||||
# Should have required indicator
|
||||
assert html =~ "required" or html =~ "Required"
|
||||
assert html =~ "Required" or html =~ "Optional"
|
||||
end
|
||||
|
||||
test "does not mark optional fields as required", %{conn: conn} do
|
||||
test "when first_name is set required in settings, table shows Required", %{conn: conn} do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
{:ok, _} =
|
||||
Membership.update_single_member_field(settings,
|
||||
field: "first_name",
|
||||
show_in_overview: true,
|
||||
required: true
|
||||
)
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# Optional fields should not have required indicator
|
||||
# Check that street (optional) doesn't have required badge
|
||||
# This test verifies that only required fields show the indicator
|
||||
assert html =~ "street" or html =~ "Street"
|
||||
# First name row should show Required (and Optional for others)
|
||||
assert html =~ "First name" or html =~ "first_name"
|
||||
assert html =~ "Required"
|
||||
end
|
||||
|
||||
test "optional fields show Optional when not required in settings", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# Email is required; other fields default to optional
|
||||
assert html =~ "Optional"
|
||||
assert html =~ "Required"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,23 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
require Ash.Query
|
||||
|
||||
describe "error handling - flash messages" do
|
||||
setup do
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
saved_visibility = settings.member_field_visibility || %{}
|
||||
saved_required = settings.member_field_required || %{}
|
||||
|
||||
on_exit(fn ->
|
||||
{:ok, s} = Mv.Membership.get_settings()
|
||||
|
||||
Mv.Membership.update_settings(s, %{
|
||||
member_field_visibility: saved_visibility,
|
||||
member_field_required: saved_required
|
||||
})
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@describetag :ui
|
||||
test "shows flash message when member creation fails with validation error", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
|
@ -74,6 +91,36 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
|||
html =~ "Please correct" or html =~ "Bitte korrigieren"
|
||||
end
|
||||
|
||||
@tag :ui
|
||||
test "shows validation error when settings-required field is missing", %{conn: conn} do
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
||||
{:ok, _} =
|
||||
Mv.Membership.update_single_member_field(settings,
|
||||
field: "first_name",
|
||||
show_in_overview: true,
|
||||
required: true
|
||||
)
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members/new")
|
||||
|
||||
# Submit without first_name (required in settings)
|
||||
form_data = %{
|
||||
"member[last_name]" => "User",
|
||||
"member[email]" => "newuser#{System.unique_integer([:positive])}@example.com"
|
||||
}
|
||||
|
||||
html =
|
||||
view
|
||||
|> form("#member-form", form_data)
|
||||
|> render_submit()
|
||||
|
||||
assert html =~ "error" or html =~ "Error" or html =~ "Fehler" or
|
||||
html =~ "first_name" or html =~ "First name" or html =~ "can't be blank" or
|
||||
html =~ "darf nicht leer sein"
|
||||
end
|
||||
|
||||
test "shows flash message when member update fails", %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# Ensure tests never hit the real Vereinfacht API (e.g. when .env is loaded by just).
|
||||
# Tests that need "configured" sync set a fake URL (127.0.0.1:1) in their own setup.
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
|
||||
ExUnit.start(
|
||||
# shows 10 slowest tests at the end of the test run
|
||||
# slowest: 10
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue