Merge branch 'main' into feat/299_plz
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
carla 2026-02-24 11:44:19 +01:00
commit 9a7608f9a1
16 changed files with 573 additions and 88 deletions

View file

@ -199,6 +199,43 @@ defmodule Mv.Membership.MembersCSVTest do
assert csv =~ "M,m@m.com,Paid"
end
test "membership_fee_type column exports fee type name" do
columns = [
%{header: "First Name", kind: :member_field, key: "first_name"},
%{header: "Email", kind: :member_field, key: "email"},
%{header: "Fee Type", kind: :membership_fee_type, key: :membership_fee_type}
]
member = %{
first_name: "M",
email: "m@m.com",
membership_fee_type: %{id: "ft-1", name: "Standard"}
}
iodata = MembersCSV.export([member], columns)
csv = IO.iodata_to_binary(iodata)
assert csv =~ "Fee Type"
assert csv =~ "Standard"
assert csv =~ "M,m@m.com,Standard"
end
test "membership_fee_type column exports empty when no fee type" do
columns = [
%{header: "First Name", kind: :member_field, key: "first_name"},
%{header: "Fee Type", kind: :membership_fee_type, key: :membership_fee_type}
]
member = %{first_name: "M", email: "m@m.com", membership_fee_type: nil}
iodata = MembersCSV.export([member], columns)
csv = IO.iodata_to_binary(iodata)
assert csv =~ "Fee Type"
assert csv =~ "M,"
refute csv =~ "Standard"
end
test "CSV injection: formula-like and dangerous prefixes are escaped with apostrophe" do
member = %{
first_name: "=SUM(A1:A10)",

View file

@ -146,6 +146,70 @@ defmodule MvWeb.MemberExportControllerTest do
refute header =~ "unknown_field"
end
test "export includes membership_fee_type column when requested", %{
conn: conn,
member1: m1
} do
payload = %{
"selected_ids" => [m1.id],
"member_fields" => ["first_name", "membership_fee_type", "email"],
"custom_field_ids" => [],
"query" => nil,
"sort_field" => nil,
"sort_order" => nil
}
conn = get(conn, "/members")
csrf_token = csrf_token_from_conn(conn)
conn =
post(conn, "/members/export.csv", %{
"payload" => Jason.encode!(payload),
"_csrf_token" => csrf_token
})
assert conn.status == 200
body = response(conn, 200)
header = body |> export_lines() |> hd()
# Fee Type column is included (label from MemberFields.label(:membership_fee_type))
assert header =~ "Fee Type"
assert body =~ "Alice"
end
# Regression: when membership_fee_start_date is not in member_fields, Fee Type must still be exported (append fallback)
test "export includes Fee Type when only first_name and membership_fee_type are requested (no start_date)",
%{
conn: conn,
member1: m1
} do
payload = %{
"selected_ids" => [m1.id],
"member_fields" => ["first_name", "membership_fee_type"],
"custom_field_ids" => [],
"query" => nil,
"sort_field" => nil,
"sort_order" => nil
}
conn = get(conn, "/members")
csrf_token = csrf_token_from_conn(conn)
conn =
post(conn, "/members/export.csv", %{
"payload" => Jason.encode!(payload),
"_csrf_token" => csrf_token
})
assert conn.status == 200
body = response(conn, 200)
header = body |> export_lines() |> hd()
assert header =~ "Fee Type"
assert header =~ "First Name"
assert body =~ "Alice"
end
test "export includes membership_fee_status computed field when requested", %{
conn: conn,
member1: m1
@ -501,4 +565,40 @@ defmodule MvWeb.MemberExportControllerTest do
assert membership_idx < active_idx
end
end
describe "POST /members/export.pdf" do
test "PDF export includes Fee Type column when requested without membership_fee_start_date",
%{
conn: conn
} do
m =
Fixtures.member_fixture(%{first_name: "PDF", last_name: "Test", email: "pdf@example.com"})
payload = %{
"selected_ids" => [m.id],
"member_fields" => ["first_name", "membership_fee_type"],
"custom_field_ids" => [],
"query" => nil,
"sort_field" => nil,
"sort_order" => nil
}
conn = get(conn, "/members")
csrf_token = csrf_token_from_conn(conn)
conn =
post(conn, "/members/export.pdf", %{
"payload" => Jason.encode!(payload),
"_csrf_token" => csrf_token
})
assert conn.status == 200
assert get_resp_header(conn, "content-type") |> List.first() =~ "application/pdf"
body = response(conn, 200)
# PDF is generated successfully with Fee Type in columns (regression: fee type without start_date)
assert is_binary(body) and byte_size(body) > 0
end
end
end

View file

@ -56,6 +56,14 @@ defmodule MvWeb.MemberLive.Index.FieldVisibilityTest do
assert field in result
end)
end
test "includes pseudo member fields (membership_fee_status, membership_fee_type, groups)" do
result = FieldVisibility.get_all_available_fields([])
assert :membership_fee_status in result
assert :membership_fee_type in result
assert :groups in result
end
end
describe "merge_with_global_settings/3" do
@ -278,6 +286,20 @@ defmodule MvWeb.MemberLive.Index.FieldVisibilityTest do
test "handles invalid input" do
assert FieldVisibility.get_visible_member_fields(nil) == []
end
test "returns membership_fee_type when visible in selection" do
selection = %{
"first_name" => true,
"membership_fee_type" => true,
"groups" => false
}
result = FieldVisibility.get_visible_member_fields(selection)
assert :membership_fee_type in result
assert :first_name in result
refute :groups in result
end
end
describe "get_visible_custom_fields/1" do

View file

@ -361,6 +361,18 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
assert html =~ "Alice"
end
test "URL with only custom field keeps custom field visible (no invalid fallback)", %{
conn: conn,
custom_field: custom_field
} do
conn = conn_with_oidc_user(conn)
id = custom_field.id
{:ok, _view, html} = live(conn, "/members?fields=custom_field_#{id}")
# Selection must not be treated as invalid; custom field column stays visible
assert html =~ "M001" or html =~ custom_field.name
end
test "handles rapid toggling", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")