test: add comprehensive tests for membership fee UI components

Add tests for all membership fee UI components following TDD principles:
This commit is contained in:
Moritz 2025-12-16 11:13:09 +01:00
parent bc989422e2
commit 5789079ab0
Signed by: moritz
GPG key ID: 1020A035E5DD0824
9 changed files with 1695 additions and 0 deletions

View file

@ -0,0 +1,232 @@
defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
@moduledoc """
Tests for membership fees section in member detail view.
"""
use MvWeb.ConnCase, async: false
import Phoenix.LiveViewTest
alias Mv.Membership.Member
alias Mv.MembershipFees.MembershipFeeType
alias Mv.MembershipFees.MembershipFeeCycle
require Ash.Query
setup do
# Create admin user
{:ok, user} =
Mv.Accounts.User
|> Ash.Changeset.for_create(:register_with_password, %{
email: "admin#{System.unique_integer([:positive])}@mv.local",
password: "testpassword123"
})
|> Ash.create()
conn = log_in_user(build_conn(), user)
%{conn: conn, user: user}
end
# Helper to create a membership fee type
defp create_fee_type(attrs) do
default_attrs = %{
name: "Test Fee Type #{System.unique_integer([:positive])}",
amount: Decimal.new("50.00"),
interval: :yearly
}
attrs = Map.merge(default_attrs, attrs)
MembershipFeeType
|> Ash.Changeset.for_create(:create, attrs)
|> Ash.create!()
end
# Helper to create a member
defp create_member(attrs) do
default_attrs = %{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!()
end
# Helper to create a cycle
defp create_cycle(member, fee_type, attrs) do
default_attrs = %{
cycle_start: ~D[2023-01-01],
amount: Decimal.new("50.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id,
status: :unpaid
}
attrs = Map.merge(default_attrs, attrs)
MembershipFeeCycle
|> Ash.Changeset.for_create(:create, attrs)
|> Ash.create!()
end
describe "cycles table display" do
test "displays all cycles for member", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
_cycle1 = create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid})
_cycle2 = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
# Should show cycles table
assert html =~ "Membership Fees" || html =~ "Mitgliedsbeiträge"
assert html =~ "2022" || html =~ "2023"
end
test "table columns show correct data", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly, amount: Decimal.new("60.00")})
member = create_member(%{membership_fee_type_id: fee_type.id})
create_cycle(member, fee_type, %{
cycle_start: ~D[2023-01-01],
amount: Decimal.new("60.00"),
status: :paid
})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
# Should show interval, amount, status
assert html =~ "Yearly" || html =~ "Jährlich"
assert html =~ "60" || html =~ "60,00"
assert html =~ "paid" || html =~ "bezahlt"
end
end
describe "membership fee type dropdown" do
test "shows only same-interval types", %{conn: conn} do
yearly_type = create_fee_type(%{interval: :yearly, name: "Yearly Type"})
_monthly_type = create_fee_type(%{interval: :monthly, name: "Monthly Type"})
member = create_member(%{membership_fee_type_id: yearly_type.id})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
# Should show yearly type but not monthly
assert html =~ "Yearly Type"
refute html =~ "Monthly Type"
end
test "shows warning if different interval selected", %{conn: conn} do
yearly_type = create_fee_type(%{interval: :yearly, name: "Yearly Type"})
monthly_type = create_fee_type(%{interval: :monthly, name: "Monthly Type"})
member = create_member(%{membership_fee_type_id: yearly_type.id})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
# Try to select monthly type (should show warning)
# Note: This test may need adjustment based on actual implementation
html =
view
|> form("form", %{"membership_fee_type_id" => monthly_type.id})
|> render_change()
assert html =~ "Warning" || html =~ "Warnung" || html =~ "not allowed"
end
end
describe "status change actions" do
test "mark as paid works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
# Mark as paid
view
|> element("button[phx-click='mark_as_paid'][phx-value-cycle-id='#{cycle.id}']")
|> render_click()
# Verify cycle is now paid
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
assert updated_cycle.status == :paid
end
test "mark as suspended works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
# Mark as suspended
view
|> element("button[phx-click='mark_as_suspended'][phx-value-cycle-id='#{cycle.id}']")
|> render_click()
# Verify cycle is now suspended
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
assert updated_cycle.status == :suspended
end
test "mark as unpaid works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
# Mark as unpaid
view
|> element("button[phx-click='mark_as_unpaid'][phx-value-cycle-id='#{cycle.id}']")
|> render_click()
# Verify cycle is now unpaid
updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id))
assert updated_cycle.status == :unpaid
end
end
describe "cycle regeneration" do
test "manual regeneration works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
# Trigger regeneration
view
|> element("button[phx-click='regenerate_cycles']")
|> render_click()
# Should have cycles generated
cycles =
MembershipFeeCycle
|> Ash.Query.filter(member_id == ^member.id)
|> Ash.read!()
assert length(cycles) > 0
end
end
describe "edge cases" do
test "handles members without membership fee type gracefully", %{conn: conn} do
# No fee type
member = create_member(%{})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
# Should not crash
assert html =~ member.first_name
end
end
end